Code

new version of gosa-si-server
[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 ($messaging_db_loop_delay);
68 my ($known_modules);
69 my ($pid_file, $procid, $pid, $log_file);
70 my ($arp_activ, $arp_fifo);
71 my ($xml);
72 my $sources_list;
73 my $max_clients;
74 my %repo_files=();
75 my $repo_path;
76 my %repo_dirs=();
77 # variables declared in config file are always set to 'our'
78 our (%cfg_defaults, $log_file, $pid_file, 
79     $server_ip, $server_port, $SIPackages_key, 
80     $arp_activ, $gosa_unit_tag,
81     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
82 );
84 # additional variable which should be globaly accessable
85 our $server_address;
86 our $server_mac_address;
87 our $bus_address;
88 our $gosa_address;
89 our $no_bus;
90 our $no_arp;
91 our $verbose;
92 our $forground;
93 our $cfg_file;
94 #our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
95 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
98 # specifies the verbosity of the daemon_log
99 $verbose = 0 ;
101 # if foreground is not null, script will be not forked to background
102 $foreground = 0 ;
104 # specifies the timeout seconds while checking the online status of a registrating client
105 $ping_timeout = 5;
107 $no_bus = 0;
108 $bus_activ = "true";
109 $no_arp = 0;
110 my $packages_list_under_construction = 0;
112 our $prg= basename($0);
114 # holds all gosa jobs
115 our $job_db;
116 our $job_queue_tn = 'jobs';
117 my $job_queue_file_name;
118 my @job_queue_col_names = ("id INTEGER", 
119                 "timestamp", 
120                 "status DEFAULT 'none'", 
121                 "result DEFAULT 'none'", 
122                 "progress DEFAULT 'none'", 
123                 "headertag DEFAULT 'none'", 
124                 "targettag DEFAULT 'none'", 
125                 "xmlmessage DEFAULT 'none'", 
126                 "macaddress DEFAULT 'none'",
127                 "plainname DEFAULT 'none'",
128                 );
130 # holds all other gosa-sd as well as the gosa-sd-bus
131 our $known_server_db;
132 our $known_server_tn = "known_server";
133 my $known_server_file_name;
134 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
136 # holds all registrated clients
137 our $known_clients_db;
138 our $known_clients_tn = "known_clients";
139 my $known_clients_file_name;
140 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
142 # holds all logged in user at each client 
143 our $login_users_db;
144 our $login_users_tn = "login_users";
145 my $login_users_file_name;
146 my @login_users_col_names = ('client', 'user', 'timestamp');
148 # holds all fai server, the debian release and tag
149 our $fai_server_db;
150 our $fai_server_tn = "fai_server"; 
151 my $fai_server_file_name;
152 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag'); 
154 our $fai_release_db;
155 our $fai_release_tn = "fai_release"; 
156 my $fai_release_file_name;
157 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state'); 
159 # holds all packages available from different repositories
160 our $packages_list_db;
161 our $packages_list_tn = "packages_list";
162 my $packages_list_file_name;
163 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
164 my $outdir = "/tmp/packages_list_db";
165 my $arch = "i386"; 
167 # holds all messages which should be delivered to a user
168 our $messaging_db;
169 our $messaging_tn = "messaging"; 
170 our @messaging_col_names = ('subject', 'message_from', 'message_to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER' );
171 my $messaging_file_name;
173 # path to directory to store client install log files
174 our $client_fai_log_dir = "/var/log/fai"; 
176 # queue which stores taskes until one of the $max_children children are ready to process the task
177 my @tasks = qw();
178 my $max_children = 2;
181 %cfg_defaults = (
182 "general" => {
183     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
184     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
185     },
186 "bus" => {
187     "activ" => [\$bus_activ, "true"],
188     },
189 "server" => {
190     "port" => [\$server_port, "20081"],
191     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
192     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
193     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
194     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
195     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
196     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
197     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
198     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
199     "repo-path" => [\$repo_path, '/srv/www/repository'],
200     "ldap-uri" => [\$ldap_uri, ""],
201     "ldap-base" => [\$ldap_base, ""],
202     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
203     "ldap-admin-password" => [\$ldap_admin_password, ""],
204     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
205     "max-clients" => [\$max_clients, 10],
206     },
207 "GOsaPackages" => {
208     "ip" => [\$gosa_ip, "0.0.0.0"],
209     "port" => [\$gosa_port, "20082"],
210     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
211     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
212     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
213     "key" => [\$GosaPackages_key, "none"],
214     },
215 "SIPackages" => {
216     "key" => [\$SIPackages_key, "none"],
217     },
218 );
221 #===  FUNCTION  ================================================================
222 #         NAME:  usage
223 #   PARAMETERS:  nothing
224 #      RETURNS:  nothing
225 #  DESCRIPTION:  print out usage text to STDERR
226 #===============================================================================
227 sub usage {
228     print STDERR << "EOF" ;
229 usage: $prg [-hvf] [-c config]
231            -h        : this (help) message
232            -c <file> : config file
233            -f        : foreground, process will not be forked to background
234            -v        : be verbose (multiple to increase verbosity)
235            -no-bus   : starts $prg without connection to bus
236            -no-arp   : starts $prg without connection to arp module
237  
238 EOF
239     print "\n" ;
243 #===  FUNCTION  ================================================================
244 #         NAME:  read_configfile
245 #   PARAMETERS:  cfg_file - string -
246 #      RETURNS:  nothing
247 #  DESCRIPTION:  read cfg_file and set variables
248 #===============================================================================
249 sub read_configfile {
250     my $cfg;
251     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
252         if( -r $cfg_file ) {
253             $cfg = Config::IniFiles->new( -file => $cfg_file );
254         } else {
255             print STDERR "Couldn't read config file!\n";
256         }
257     } else {
258         $cfg = Config::IniFiles->new() ;
259     }
260     foreach my $section (keys %cfg_defaults) {
261         foreach my $param (keys %{$cfg_defaults{ $section }}) {
262             my $pinfo = $cfg_defaults{ $section }{ $param };
263             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
264         }
265     }
269 #===  FUNCTION  ================================================================
270 #         NAME:  logging
271 #   PARAMETERS:  level - string - default 'info'
272 #                msg - string -
273 #                facility - string - default 'LOG_DAEMON'
274 #      RETURNS:  nothing
275 #  DESCRIPTION:  function for logging
276 #===============================================================================
277 sub daemon_log {
278     # log into log_file
279     my( $msg, $level ) = @_;
280     if(not defined $msg) { return }
281     if(not defined $level) { $level = 1 }
282     if(defined $log_file){
283         open(LOG_HANDLE, ">>$log_file");
284         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
285             print STDERR "cannot open $log_file: $!";
286             return }
287             chomp($msg);
288             if($level <= $verbose){
289                 my ($seconds, $minutes, $hours, $monthday, $month,
290                         $year, $weekday, $yearday, $sommertime) = localtime(time);
291                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
292                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
293                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
294                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
295                 $month = $monthnames[$month];
296                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
297                 $year+=1900;
298                 my $name = $prg;
300                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
301                 print LOG_HANDLE $log_msg;
302                 if( $foreground ) { 
303                     print STDERR $log_msg;
304                 }
305             }
306         close( LOG_HANDLE );
307     }
311 #===  FUNCTION  ================================================================
312 #         NAME:  check_cmdline_param
313 #   PARAMETERS:  nothing
314 #      RETURNS:  nothing
315 #  DESCRIPTION:  validates commandline parameter
316 #===============================================================================
317 sub check_cmdline_param () {
318     my $err_config;
319     my $err_counter = 0;
320         if(not defined($cfg_file)) {
321                 $cfg_file = "/etc/gosa-si/server.conf";
322                 if(! -r $cfg_file) {
323                         $err_config = "please specify a config file";
324                         $err_counter += 1;
325                 }
326     }
327     if( $err_counter > 0 ) {
328         &usage( "", 1 );
329         if( defined( $err_config)) { print STDERR "$err_config\n"}
330         print STDERR "\n";
331         exit( -1 );
332     }
336 #===  FUNCTION  ================================================================
337 #         NAME:  check_pid
338 #   PARAMETERS:  nothing
339 #      RETURNS:  nothing
340 #  DESCRIPTION:  handels pid processing
341 #===============================================================================
342 sub check_pid {
343     $pid = -1;
344     # Check, if we are already running
345     if( open(LOCK_FILE, "<$pid_file") ) {
346         $pid = <LOCK_FILE>;
347         if( defined $pid ) {
348             chomp( $pid );
349             if( -f "/proc/$pid/stat" ) {
350                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
351                 if( $stat ) {
352                                         daemon_log("ERROR: Already running",1);
353                     close( LOCK_FILE );
354                     exit -1;
355                 }
356             }
357         }
358         close( LOCK_FILE );
359         unlink( $pid_file );
360     }
362     # create a syslog msg if it is not to possible to open PID file
363     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
364         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
365         if (open(LOCK_FILE, '<', $pid_file)
366                 && ($pid = <LOCK_FILE>))
367         {
368             chomp($pid);
369             $msg .= "(PID $pid)\n";
370         } else {
371             $msg .= "(unable to read PID)\n";
372         }
373         if( ! ($foreground) ) {
374             openlog( $0, "cons,pid", "daemon" );
375             syslog( "warning", $msg );
376             closelog();
377         }
378         else {
379             print( STDERR " $msg " );
380         }
381         exit( -1 );
382     }
385 #===  FUNCTION  ================================================================
386 #         NAME:  import_modules
387 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
388 #                are stored
389 #      RETURNS:  nothing
390 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
391 #                state is on is imported by "require 'file';"
392 #===============================================================================
393 sub import_modules {
394     daemon_log(" ", 1);
396     if (not -e $modules_path) {
397         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
398     }
400     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
401     while (defined (my $file = readdir (DIR))) {
402         if (not $file =~ /(\S*?).pm$/) {
403             next;
404         }
405                 my $mod_name = $1;
407         if( $file =~ /ArpHandler.pm/ ) {
408             if( $no_arp > 0 ) {
409                 next;
410             }
411         }
412         
413         eval { require $file; };
414         if ($@) {
415             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
416             daemon_log("$@", 5);
417                 } else {
418                         my $info = eval($mod_name.'::get_module_info()');
419                         # Only load module if get_module_info() returns a non-null object
420                         if( $info ) {
421                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
422                                 $known_modules->{$mod_name} = $info;
423                                 daemon_log("INFO: module $mod_name loaded", 5);
424                         }
425                 }
426     }   
427     close (DIR);
431 #===  FUNCTION  ================================================================
432 #         NAME:  sig_int_handler
433 #   PARAMETERS:  signal - string - signal arose from system
434 #      RETURNS:  noting
435 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
436 #===============================================================================
437 sub sig_int_handler {
438     my ($signal) = @_;
440 #       if (defined($ldap_handle)) {
441 #               $ldap_handle->disconnect;
442 #       }
443     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
444     
446     daemon_log("shutting down gosa-si-server", 1);
447     system("kill `ps -C gosa-si-server -o pid=`");
449 $SIG{INT} = \&sig_int_handler;
452 sub check_key_and_xml_validity {
453     my ($crypted_msg, $module_key, $session_id) = @_;
454     my $msg;
455     my $msg_hash;
456     my $error_string;
457     eval{
458         $msg = &decrypt_msg($crypted_msg, $module_key);
460         if ($msg =~ /<xml>/i){
461             $msg =~ s/\s+/ /g;  # just for better daemon_log
462             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
463             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
465             ##############
466             # check header
467             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
468             my $header_l = $msg_hash->{'header'};
469             if( 1 > @{$header_l} ) { die 'empty header tag'; }
470             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
471             my $header = @{$header_l}[0];
472             if( 0 == length $header) { die 'empty string in header tag'; }
474             ##############
475             # check source
476             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
477             my $source_l = $msg_hash->{'source'};
478             if( 1 > @{$source_l} ) { die 'empty source tag'; }
479             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
480             my $source = @{$source_l}[0];
481             if( 0 == length $source) { die 'source error'; }
483             ##############
484             # check target
485             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
486             my $target_l = $msg_hash->{'target'};
487             if( 1 > @{$target_l} ) { die 'empty target tag'; }
488         }
489     };
490     if($@) {
491         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
492         $msg = undef;
493         $msg_hash = undef;
494     }
496     return ($msg, $msg_hash);
500 sub check_outgoing_xml_validity {
501     my ($msg) = @_;
503     my $msg_hash;
504     eval{
505         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
507         ##############
508         # check header
509         my $header_l = $msg_hash->{'header'};
510         if( 1 != @{$header_l} ) {
511             die 'no or more than one headers specified';
512         }
513         my $header = @{$header_l}[0];
514         if( 0 == length $header) {
515             die 'header has length 0';
516         }
518         ##############
519         # check source
520         my $source_l = $msg_hash->{'source'};
521         if( 1 != @{$source_l} ) {
522             die 'no or more than 1 sources specified';
523         }
524         my $source = @{$source_l}[0];
525         if( 0 == length $source) {
526             die 'source has length 0';
527         }
528         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
529                 $source =~ /^GOSA$/i ) {
530             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
531         }
532         
533         ##############
534         # check target  
535         my $target_l = $msg_hash->{'target'};
536         if( 0 == @{$target_l} ) {
537             die "no targets specified";
538         }
539         foreach my $target (@$target_l) {
540             if( 0 == length $target) {
541                 die "target has length 0";
542             }
543             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
544                     $target =~ /^GOSA$/i ||
545                     $target =~ /^\*$/ ||
546                     $target =~ /KNOWN_SERVER/i ||
547                     $target =~ /JOBDB/i ||
548                     $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 ){
549                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
550             }
551         }
552     };
553     if($@) {
554         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
555         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
556         $msg_hash = undef;
557     }
559     return ($msg_hash);
563 sub input_from_known_server {
564     my ($input, $remote_ip, $session_id) = @_ ;  
565     my ($msg, $msg_hash, $module);
567     my $sql_statement= "SELECT * FROM known_server";
568     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
570     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
571         my $host_name = $hit->{hostname};
572         if( not $host_name =~ "^$remote_ip") {
573             next;
574         }
575         my $host_key = $hit->{hostkey};
576         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
577         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
579         # check if module can open msg envelope with module key
580         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
581         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
582             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
583             daemon_log("$@", 8);
584             next;
585         }
586         else {
587             $msg = $tmp_msg;
588             $msg_hash = $tmp_msg_hash;
589             $module = "SIPackages";
590             last;
591         }
592     }
594     if( (!$msg) || (!$msg_hash) || (!$module) ) {
595         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
596     }
597   
598     return ($msg, $msg_hash, $module);
602 sub input_from_known_client {
603     my ($input, $remote_ip, $session_id) = @_ ;  
604     my ($msg, $msg_hash, $module);
606     my $sql_statement= "SELECT * FROM known_clients";
607     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
608     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
609         my $host_name = $hit->{hostname};
610         if( not $host_name =~ /^$remote_ip:\d*$/) {
611                 next;
612                 }
613         my $host_key = $hit->{hostkey};
614         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
615         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
617         # check if module can open msg envelope with module key
618         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
620         if( (!$msg) || (!$msg_hash) ) {
621             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
622             &daemon_log("$@", 8);
623             next;
624         }
625         else {
626             $module = "SIPackages";
627             last;
628         }
629     }
631     if( (!$msg) || (!$msg_hash) || (!$module) ) {
632         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
633     }
635     return ($msg, $msg_hash, $module);
639 sub input_from_unknown_host {
640     no strict "refs";
641     my ($input, $session_id) = @_ ;
642     my ($msg, $msg_hash, $module);
643     my $error_string;
644     
645         my %act_modules = %$known_modules;
647         while( my ($mod, $info) = each(%act_modules)) {
649         # check a key exists for this module
650         my $module_key = ${$mod."_key"};
651         if( not defined $module_key ) {
652             if( $mod eq 'ArpHandler' ) {
653                 next;
654             }
655             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
656             next;
657         }
658         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
660         # check if module can open msg envelope with module key
661         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
662         if( (not defined $msg) || (not defined $msg_hash) ) {
663             next;
664         }
665         else {
666             $module = $mod;
667             last;
668         }
669     }
671     if( (!$msg) || (!$msg_hash) || (!$module)) {
672         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
673     }
675     return ($msg, $msg_hash, $module);
679 sub create_ciphering {
680     my ($passwd) = @_;
681         if((!defined($passwd)) || length($passwd)==0) {
682                 $passwd = "";
683         }
684     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
685     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
686     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
687     $my_cipher->set_iv($iv);
688     return $my_cipher;
692 sub encrypt_msg {
693     my ($msg, $key) = @_;
694     my $my_cipher = &create_ciphering($key);
695     my $len;
696     {
697             use bytes;
698             $len= 16-length($msg)%16;
699     }
700     $msg = "\0"x($len).$msg;
701     $msg = $my_cipher->encrypt($msg);
702     chomp($msg = &encode_base64($msg));
703     # there are no newlines allowed inside msg
704     $msg=~ s/\n//g;
705     return $msg;
709 sub decrypt_msg {
711     my ($msg, $key) = @_ ;
712     $msg = &decode_base64($msg);
713     my $my_cipher = &create_ciphering($key);
714     $msg = $my_cipher->decrypt($msg); 
715     $msg =~ s/\0*//g;
716     return $msg;
720 sub get_encrypt_key {
721     my ($target) = @_ ;
722     my $encrypt_key;
723     my $error = 0;
725     # target can be in known_server
726     if( not defined $encrypt_key ) {
727         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
728         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
729         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
730             my $host_name = $hit->{hostname};
731             if( $host_name ne $target ) {
732                 next;
733             }
734             $encrypt_key = $hit->{hostkey};
735             last;
736         }
737     }
739     # target can be in known_client
740     if( not defined $encrypt_key ) {
741         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
742         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
743         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
744             my $host_name = $hit->{hostname};
745             if( $host_name ne $target ) {
746                 next;
747             }
748             $encrypt_key = $hit->{hostkey};
749             last;
750         }
751     }
753     return $encrypt_key;
757 #===  FUNCTION  ================================================================
758 #         NAME:  open_socket
759 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
760 #                [PeerPort] string necessary if port not appended by PeerAddr
761 #      RETURNS:  socket IO::Socket::INET
762 #  DESCRIPTION:  open a socket to PeerAddr
763 #===============================================================================
764 sub open_socket {
765     my ($PeerAddr, $PeerPort) = @_ ;
766     if(defined($PeerPort)){
767         $PeerAddr = $PeerAddr.":".$PeerPort;
768     }
769     my $socket;
770     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
771             Porto => "tcp",
772             Type => SOCK_STREAM,
773             Timeout => 5,
774             );
775     if(not defined $socket) {
776         return;
777     }
778 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
779     return $socket;
783 #===  FUNCTION  ================================================================
784 #         NAME:  get_ip 
785 #   PARAMETERS:  interface name (i.e. eth0)
786 #      RETURNS:  (ip address) 
787 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
788 #===============================================================================
789 sub get_ip {
790         my $ifreq= shift;
791         my $result= "";
792         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
793         my $proto= getprotobyname('ip');
795         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
796                 or die "socket: $!";
798         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
799                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
800                 my ($port, $addr) = sockaddr_in $sin;
801                 my $ip            = inet_ntoa $addr;
803                 if ($ip && length($ip) > 0) {
804                         $result = $ip;
805                 }
806         }
808         return $result;
812 sub get_local_ip_for_remote_ip {
813         my $remote_ip= shift;
814         my $result="0.0.0.0";
816         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
817                 if($remote_ip eq "127.0.0.1") {
818                         $result = "127.0.0.1";
819                 } else {
820                         my $PROC_NET_ROUTE= ('/proc/net/route');
822                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
823                                 or die "Could not open $PROC_NET_ROUTE";
825                         my @ifs = <PROC_NET_ROUTE>;
827                         close(PROC_NET_ROUTE);
829                         # Eat header line
830                         shift @ifs;
831                         chomp @ifs;
832                         foreach my $line(@ifs) {
833                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
834                                 my $destination;
835                                 my $mask;
836                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
837                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
838                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
839                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
840                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
841                                         # destination matches route, save mac and exit
842                                         $result= &get_ip($Iface);
843                                         last;
844                                 }
845                         }
846                 }
847         } else {
848                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
849         }
850         return $result;
854 sub send_msg_to_target {
855     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
856     my $error = 0;
857     my $header;
858     my $new_status;
859     my $act_status;
860     my ($sql_statement, $res);
861   
862     if( $msg_header ) {
863         $header = "'$msg_header'-";
864     } else {
865         $header = "";
866     }
868         # Patch the source ip
869         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
870                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
871                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
872         }
874     # encrypt xml msg
875     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
877     # opensocket
878     my $socket = &open_socket($address);
879     if( !$socket ) {
880         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
881         $error++;
882     }
883     
884     if( $error == 0 ) {
885         # send xml msg
886         print $socket $crypted_msg."\n";
888         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
889         #daemon_log("DEBUG: message:\n$msg", 9);
890         
891     }
893     # close socket in any case
894     if( $socket ) {
895         close $socket;
896     }
898     if( $error > 0 ) { $new_status = "down"; }
899     else { $new_status = $msg_header; }
902     # known_clients
903     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
904     $res = $known_clients_db->select_dbentry($sql_statement);
905     if( keys(%$res) > 0) {
906         $act_status = $res->{1}->{'status'};
907         if( $act_status eq "down" ) {
908             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
909             $res = $known_clients_db->del_dbentry($sql_statement);
910             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
911         } else { 
912             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
913             $res = $known_clients_db->update_dbentry($sql_statement);
914             if($new_status eq "down"){
915                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
916             } else {
917                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
918             }
919         }
920     }
922     # known_server
923     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
924     $res = $known_server_db->select_dbentry($sql_statement);
925     if( keys(%$res) > 0 ) {
926         $act_status = $res->{1}->{'status'};
927         if( $act_status eq "down" ) {
928             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
929             $res = $known_server_db->del_dbentry($sql_statement);
930             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
931         } 
932         else { 
933             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
934             $res = $known_server_db->update_dbentry($sql_statement);
935             if($new_status eq "down"){
936                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
937             }
938             else {
939                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
940             }
941         }
942     }
943     return $error; 
947 sub update_jobdb_status_for_send_msgs {
948     my ($answer, $error) = @_;
949     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
950         my $jobdb_id = $1;
951             
952         # sending msg faild
953         if( $error ) {
954             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
955                 my $sql_statement = "UPDATE $job_queue_tn ".
956                     "SET status='error', result='can not deliver msg, please consult log file' ".
957                     "WHERE id='$jobdb_id'";
958                 my $res = $job_db->update_dbentry($sql_statement);
959             }
961         # sending msg was successful
962         } else {
963             my $sql_statement = "UPDATE $job_queue_tn ".
964                 "SET status='done' ".
965                 "WHERE id='$jobdb_id' AND status='processed'";
966             my $res = $job_db->update_dbentry($sql_statement);
967         }
968     }
971 sub _start {
972     my ($kernel) = $_[KERNEL];
973     &trigger_db_loop($kernel);
974     $global_kernel = $kernel;
975         $kernel->yield('create_fai_server_db', $fai_server_tn );
976         $kernel->yield('create_fai_release_db', $fai_release_tn );
977         $kernel->sig(USR1 => "sig_handler");
978         $kernel->sig(USR2 => "create_packages_list_db");
981 sub sig_handler {
982         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
983         daemon_log("0 INFO got signal '$signal'", 1); 
984         $kernel->sig_handled();
985         return;
988 sub next_task {
989     my ($session, $heap) = @_[SESSION, HEAP];
991     while ( keys( %{ $heap->{task} } ) < $max_children ) {
992         my $next_task = shift @tasks;
993         last unless defined $next_task;
995         my $task = POE::Wheel::Run->new(
996                 Program => sub { process_task($session, $heap, $next_task) },
997                 StdioFilter => POE::Filter::Reference->new(),
998                 StdoutEvent  => "task_result",
999                 StderrEvent  => "task_debug",
1000                 CloseEvent   => "task_done",
1001                );
1003         $heap->{task}->{ $task->ID } = $task;
1004     }
1007 sub handle_task_result {
1008     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1009     my $client_answer = $result->{'answer'};
1010     if( $client_answer =~ s/session_id=(\d+)$// ) {
1011         my $session_id = $1;
1012         if( defined $session_id ) {
1013             my $session_reference = $kernel->ID_id_to_session($session_id);
1014             if( defined $session_reference ) {
1015                 $heap = $session_reference->get_heap();
1016             }
1017         }
1019         if(exists $heap->{'client'}) {
1020             $heap->{'client'}->put($client_answer);
1021         }
1022     }
1023     $kernel->sig(CHLD => "child_reap");
1026 sub handle_task_debug {
1027     my $result = $_[ARG0];
1028     print STDERR "$result\n";
1031 sub handle_task_done {
1032     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1033     delete $heap->{task}->{$task_id};
1034     $kernel->yield("next_task");
1037 sub process_task {
1038     no strict "refs";
1039     my ($session, $heap, $input) = @_;
1040     my $session_id = $session->ID;
1041     my ($msg, $msg_hash, $module);
1042     my $error = 0;
1043     my $answer_l;
1044     my ($answer_header, @answer_target_l, $answer_source);
1045     my $client_answer = "";
1047     daemon_log("", 5); 
1048     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1049     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1051     ####################
1052     # check incoming msg
1053     # msg is from a new client or gosa
1054     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1055     # msg is from a gosa-si-server or gosa-si-bus
1056     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1058     }
1059     # msg is from a gosa-si-client
1060     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1061         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1062     }
1063     # an error occurred
1064     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1065         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1066         # could not understand a msg from its server the client cause a re-registering process
1067         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);
1068         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1069         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1070         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1071             my $host_name = $hit->{'hostname'};
1072             my $host_key = $hit->{'hostkey'};
1073             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1074             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1075             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1076         }
1077         $error++;
1078     }
1080     ######################
1081     # process incoming msg
1082     if( $error == 0) {
1083         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1084                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1085         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1086         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1088         if ( 0 < @{$answer_l} ) {
1089             my $answer_str = join("\n", @{$answer_l});
1090             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1091         }
1092     }
1093     if( !$answer_l ) { $error++ };
1095     ########
1096     # answer
1097     if( $error == 0 ) {
1099         foreach my $answer ( @{$answer_l} ) {
1100             # for each answer in answer list
1101             
1102             # check outgoing msg to xml validity
1103             my $answer_hash = &check_outgoing_xml_validity($answer);
1104             if( not defined $answer_hash ) {
1105                 next;
1106             }
1107             
1108             $answer_header = @{$answer_hash->{'header'}}[0];
1109             @answer_target_l = @{$answer_hash->{'target'}};
1110             $answer_source = @{$answer_hash->{'source'}}[0];
1112             # deliver msg to all targets 
1113             foreach my $answer_target ( @answer_target_l ) {
1115                 # targets of msg are all gosa-si-clients in known_clients_db
1116                 if( $answer_target eq "*" ) {
1117                     # answer is for all clients
1118                     my $sql_statement= "SELECT * FROM known_clients";
1119                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1120                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1121                         my $host_name = $hit->{hostname};
1122                         my $host_key = $hit->{hostkey};
1123                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1124                         &update_jobdb_status_for_send_msgs($answer, $error);
1125                     }
1126                 }
1128                 # targets of msg are all gosa-si-server in known_server_db
1129                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1130                     # answer is for all server in known_server
1131                     my $sql_statement= "SELECT * FROM known_server";
1132                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1133                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1134                         my $host_name = $hit->{hostname};
1135                         my $host_key = $hit->{hostkey};
1136                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1137                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1138                         &update_jobdb_status_for_send_msgs($answer, $error);
1139                     }
1140                 }
1142                 # target of msg is GOsa
1143                                 elsif( $answer_target eq "GOSA" ) {
1144                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1145                                         my $add_on = "";
1146                     if( defined $session_id ) {
1147                         $add_on = ".session_id=$session_id";
1148                     }
1149                     # answer is for GOSA and has to returned to connected client
1150                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1151                     $client_answer = $gosa_answer.$add_on;
1152                 }
1154                 # target of msg is job queue at this host
1155                 elsif( $answer_target eq "JOBDB") {
1156                     $answer =~ /<header>(\S+)<\/header>/;   
1157                     my $header;
1158                     if( defined $1 ) { $header = $1; }
1159                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1160                     &update_jobdb_status_for_send_msgs($answer, $error);
1161                 }
1163                 # target of msg is a mac address
1164                 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 ) {
1165                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1166                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1167                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1168                     my $found_ip_flag = 0;
1169                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1170                         my $host_name = $hit->{hostname};
1171                         my $host_key = $hit->{hostkey};
1172                         $answer =~ s/$answer_target/$host_name/g;
1173                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1174                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1175                         &update_jobdb_status_for_send_msgs($answer, $error);
1176                         $found_ip_flag++ ;
1177                     }   
1178                     if( $found_ip_flag == 0) {
1179                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1180                         if( $bus_activ eq "true" ) { 
1181                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1182                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1183                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1184                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1185                                 my $bus_address = $hit->{hostname};
1186                                 my $bus_key = $hit->{hostkey};
1187                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1188                                 &update_jobdb_status_for_send_msgs($answer, $error);
1189                                 last;
1190                             }
1191                         }
1193                     }
1195                 #  answer is for one specific host   
1196                 } else {
1197                     # get encrypt_key
1198                     my $encrypt_key = &get_encrypt_key($answer_target);
1199                     if( not defined $encrypt_key ) {
1200                         # unknown target, forward msg to bus
1201                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1202                         if( $bus_activ eq "true" ) { 
1203                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1204                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1205                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1206                             my $res_length = keys( %{$query_res} );
1207                             if( $res_length == 0 ){
1208                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1209                                         "no bus found in known_server", 3);
1210                             }
1211                             else {
1212                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1213                                     my $bus_key = $hit->{hostkey};
1214                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1215                                     &update_jobdb_status_for_send_msgs($answer, $error);
1216                                 }
1217                             }
1218                         }
1219                         next;
1220                     }
1221                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1222                     &update_jobdb_status_for_send_msgs($answer, $error);
1223                 }
1224             }
1225         }
1226     }
1228     my $filter = POE::Filter::Reference->new();
1229     my %result = ( 
1230             status => "seems ok to me",
1231             answer => $client_answer,
1232             );
1234     my $output = $filter->put( [ \%result ] );
1235     print @$output;
1241 sub trigger_db_loop {
1242         my ($kernel) = @_ ;
1243         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1244         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1245     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1246     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1249 sub watch_for_done_jobs {
1250     my ($kernel,$heap) = @_[KERNEL, HEAP];
1252     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1253         " WHERE status='done'";
1254         my $res = $job_db->select_dbentry( $sql_statement );
1256     while( my ($id, $hit) = each %{$res} ) {
1257         my $jobdb_id = $hit->{id};
1258         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1259         my $res = $job_db->del_dbentry($sql_statement);
1260     }
1262     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1265 sub watch_for_new_jobs {
1266         my ($kernel,$heap) = @_[KERNEL, HEAP];
1268         # check gosa job queue for jobs with executable timestamp
1269         my $timestamp = &get_time();
1270         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1271         my $res = $job_db->exec_statement( $sql_statement );
1273         # Merge all new jobs that would do the same actions
1274         my @drops;
1275         my $hits;
1276         foreach my $hit (reverse @{$res} ) {
1277                 my $macaddress= lc @{$hit}[8];
1278                 my $headertag= @{$hit}[5];
1279                 if(defined($hits->{$macaddress}->{$headertag})) {
1280                         push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1281                 }
1282                 $hits->{$macaddress}->{$headertag}= $hit;
1283         }
1285         # Delete new jobs with a matching job in state 'processing'
1286         foreach my $macaddress (keys %{$hits}) {
1287                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1288                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1289                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1290                         my $res = $job_db->exec_statement( $sql_statement );
1291                         foreach my $hit (@{$res}) {
1292                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1293                         }
1294                 }
1295         }
1297         # Commit deletion
1298         $job_db->exec_statementlist(\@drops);
1300         # Look for new jobs that could be executed
1301         foreach my $macaddress (keys %{$hits}) {
1303                 # Look if there is an executing job
1304                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1305                 my $res = $job_db->exec_statement( $sql_statement );
1307                 # Skip new jobs for host if there is a processing job
1308                 if(defined($res) and defined @{$res}[0]) {
1309                         next;
1310                 }
1312                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1313                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1314                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1316                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1317                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1318                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1320                         # expect macaddress is unique!!!!!!
1321                         my $target = $res_hash->{1}->{hostname};
1323                         # change header
1324                         $job_msg =~ s/<header>job_/<header>gosa_/;
1326                         # add sqlite_id
1327                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1329                         $job_msg =~ /<header>(\S+)<\/header>/;
1330                         my $header = $1 ;
1331                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1333                         # update status in job queue to 'processing'
1334                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1335                         my $res = $job_db->update_dbentry($sql_statement);
1337                         # We don't want parallel processing
1338                         last;
1339                 }
1340         }
1342         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1346 sub watch_for_new_messages {
1347     my ($kernel,$heap) = @_[KERNEL, HEAP];
1348     my @coll_user_msg;   # collection list of outgoing messages
1349     
1350     # check messaging_db for new incoming messages with executable timestamp
1351     my $timestamp = &get_time();
1352     #my $sql_statement = "SELECT * FROM $messaging_tn WHERE (CAST (delivery_time AS INTEGER) + 120) < $timestamp";
1353     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1354     my $res = $messaging_db->exec_statement( $sql_statement );
1356         foreach my $hit (@{$res}) {
1358         # create outgoing messages
1359         my $message_to = @{$hit}[2];
1361         # translate message_to to plain login name
1362         my @reciever_l = ($message_to);  
1363         my $message_id = @{$hit}[8];
1365         #add each outgoing msg to messaging_db
1366         my $reciever;
1367         foreach $reciever (@reciever_l) {
1368             my $sql_statement = "INSERT INTO $messaging_tn (subject, message_from, message_to, flag, direction, delivery_time, message, timestamp, id) ".
1369                 "VALUES ('".
1370                 @{$hit}[0]."', '".   # subject
1371                 @{$hit}[1]."', '".   # message_from
1372                 $reciever."', '".    # message_to
1373                 "none"."', '".       # flag
1374                 "out"."', '".        # direction
1375                 @{$hit}[5]."', '".   # delivery_time
1376                 @{$hit}[6]."', '".   # message
1377                 $timestamp."', '".   # timestamp
1378                 @{$hit}[8].          # id
1379                 "')";
1380             &daemon_log("M DEBUG: $sql_statement", 1);
1381             my $res = $messaging_db->exec_statement($sql_statement);
1382             &daemon_log("M INFO: message '".@{$hit}[8]."' is prepared for delivery to reciever '$reciever'", 5);
1383         }
1385         # send outgoing messages
1386         my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1387         my $res = $messaging_db->exec_statement( $sql_statement );
1388         foreach my $hit (@{$res}) {
1389             # add subject, from, to and message to list coll_user_msg
1390             my @user_msg = [@{$hit}[0], @{$hit}[1], $reciever, @{$hit}[6]];
1391             push( @coll_user_msg, \@user_msg);
1392         }
1394         # send outgoing list to myself (gosa-si-server) to deliver each message to user
1395         # reason for this workaround: if to much messages have to be delivered, it can come to 
1396         # denial of service problems of the server. so, the incoming message list can be processed
1397         # by a forked child and gosa-si-server is always ready to work. 
1398         my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1399         # add to hash 'msg1' => [subject, from, to, message]
1400         # hash to string
1401         # send msg to myself
1402 # TODO
1404         # set incoming message to flag d=deliverd
1405         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1406         &daemon_log("M DEBUG: $sql_statement", 7);
1407         $res = $messaging_db->update_dbentry($sql_statement);
1408         &daemon_log("M INFO: message '".@{$hit}[8]."' is set to flag 'p' (processed)", 5);
1410     }
1411     
1412     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1415     return;
1419 sub watch_for_done_messages {
1420     my ($kernel,$heap) = @_[KERNEL, HEAP];
1422     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1423     return;
1427 sub get_ldap_handle {
1428         my ($session_id) = @_;
1429         my $heap;
1430         my $ldap_handle;
1432         if (not defined $session_id ) { $session_id = 0 };
1433         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1435         if ($session_id == 0) {
1436                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1437                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1438                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1440         } else {
1441                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1442                 if( defined $session_reference ) {
1443                         $heap = $session_reference->get_heap();
1444                 }
1446                 if (not defined $heap) {
1447                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1448                         return;
1449                 }
1451                 # TODO: This "if" is nonsense, because it doesn't prove that the
1452                 #       used handle is still valid - or if we've to reconnect...
1453                 #if (not exists $heap->{ldap_handle}) {
1454                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1455                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1456                         $heap->{ldap_handle} = $ldap_handle;
1457                 #}
1458         }
1459         return $ldap_handle;
1463 sub change_fai_state {
1464     my ($st, $targets, $session_id) = @_;
1465     $session_id = 0 if not defined $session_id;
1466     # Set FAI state to localboot
1467     my %mapActions= (
1468         reboot    => '',
1469         update    => 'softupdate',
1470         localboot => 'localboot',
1471         reinstall => 'install',
1472         rescan    => '',
1473         wake      => '',
1474         memcheck  => 'memcheck',
1475         sysinfo   => 'sysinfo',
1476         install   => 'install',
1477     );
1479     # Return if this is unknown
1480     if (!exists $mapActions{ $st }){
1481         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1482       return;
1483     }
1485     my $state= $mapActions{ $st };
1487     my $ldap_handle = &get_ldap_handle($session_id);
1488     if( defined($ldap_handle) ) {
1490       # Build search filter for hosts
1491         my $search= "(&(objectClass=GOhard)";
1492         foreach (@{$targets}){
1493             $search.= "(macAddress=$_)";
1494         }
1495         $search.= ")";
1497       # If there's any host inside of the search string, procress them
1498         if (!($search =~ /macAddress/)){
1499             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1500             return;
1501         }
1503       # Perform search for Unit Tag
1504       my $mesg = $ldap_handle->search(
1505           base   => $ldap_base,
1506           scope  => 'sub',
1507           attrs  => ['dn', 'FAIstate', 'objectClass'],
1508           filter => "$search"
1509           );
1511           if ($mesg->count) {
1512                   my @entries = $mesg->entries;
1513                   foreach my $entry (@entries) {
1514                           # Only modify entry if it is not set to '$state'
1515                           if ($entry->get_value("FAIstate") ne "$state"){
1516                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1517                                   my $result;
1518                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1519                                   if (exists $tmp{'FAIobject'}){
1520                                           if ($state eq ''){
1521                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1522                                                           delete => [ FAIstate => [] ] ]);
1523                                           } else {
1524                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1525                                                           replace => [ FAIstate => $state ] ]);
1526                                           }
1527                                   } elsif ($state ne ''){
1528                                           $result= $ldap_handle->modify($entry->dn, changes => [
1529                                                   add     => [ objectClass => 'FAIobject' ],
1530                                                   add     => [ FAIstate => $state ] ]);
1531                                   }
1533                                   # Errors?
1534                                   if ($result->code){
1535                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1536                                   }
1537                           } else {
1538                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1539                           }  
1540                   }
1541           }
1542     # if no ldap handle defined
1543     } else {
1544         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1545     }
1550 sub change_goto_state {
1551     my ($st, $targets, $session_id) = @_;
1552     $session_id = 0  if not defined $session_id;
1554     # Switch on or off?
1555     my $state= $st eq 'active' ? 'active': 'locked';
1557     my $ldap_handle = &get_ldap_handle($session_id);
1558     if( defined($ldap_handle) ) {
1560       # Build search filter for hosts
1561       my $search= "(&(objectClass=GOhard)";
1562       foreach (@{$targets}){
1563         $search.= "(macAddress=$_)";
1564       }
1565       $search.= ")";
1567       # If there's any host inside of the search string, procress them
1568       if (!($search =~ /macAddress/)){
1569         return;
1570       }
1572       # Perform search for Unit Tag
1573       my $mesg = $ldap_handle->search(
1574           base   => $ldap_base,
1575           scope  => 'sub',
1576           attrs  => ['dn', 'gotoMode'],
1577           filter => "$search"
1578           );
1580       if ($mesg->count) {
1581         my @entries = $mesg->entries;
1582         foreach my $entry (@entries) {
1584           # Only modify entry if it is not set to '$state'
1585           if ($entry->get_value("gotoMode") ne $state){
1587             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1588             my $result;
1589             $result= $ldap_handle->modify($entry->dn, changes => [
1590                                                 replace => [ gotoMode => $state ] ]);
1592             # Errors?
1593             if ($result->code){
1594               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1595             }
1597           }
1598         }
1599       }
1601     }
1605 sub run_create_fai_server_db {
1606     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1607     my $session_id = $session->ID;
1608     my $task = POE::Wheel::Run->new(
1609             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1610             StdoutEvent  => "session_run_result",
1611             StderrEvent  => "session_run_debug",
1612             CloseEvent   => "session_run_done",
1613             );
1615     $heap->{task}->{ $task->ID } = $task;
1616     return;
1620 sub create_fai_server_db {
1621     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1622         my $result;
1624         if (not defined $session_id) { $session_id = 0; }
1625     my $ldap_handle = &get_ldap_handle();
1626         if(defined($ldap_handle)) {
1627                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1628                 my $mesg= $ldap_handle->search(
1629                         base   => $ldap_base,
1630                         scope  => 'sub',
1631                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1632                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1633                 );
1634                 if($mesg->{'resultCode'} == 0 &&
1635                    $mesg->count != 0) {
1636                    foreach my $entry (@{$mesg->{entries}}) {
1637                            if($entry->exists('FAIrepository')) {
1638                                    # Add an entry for each Repository configured for server
1639                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1640                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1641                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1642                                                    $result= $fai_server_db->add_dbentry( { 
1643                                                                    table => $table_name,
1644                                                                    primkey => ['server', 'release', 'tag'],
1645                                                                    server => $tmp_url,
1646                                                                    release => $tmp_release,
1647                                                                    sections => $tmp_sections,
1648                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1649                                                            } );
1650                                            }
1651                                    }
1652                            }
1653                    }
1654                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1656                 # TODO: Find a way to post the 'create_packages_list_db' event
1657                 if(not defined($dont_create_packages_list)) {
1658                         #&create_packages_list_db;
1660                         # it should not be possible to trigger a recreation of packages_list_db
1661                         # while packages_list_db is under construction, so set flag packages_list_under_construction
1662                         # which is tested befor recreation can be started
1663                         &create_packages_list_db(undef, undef, $session_id);
1664                 }
1665         }       
1666     
1667     $ldap_handle->disconnect;
1668         return $result;
1672 sub run_create_fai_release_db {
1673     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1674         my $session_id = $session->ID;
1675     my $task = POE::Wheel::Run->new(
1676             Program => sub { &create_fai_release_db($table_name, $session_id) },
1677             StdoutEvent  => "session_run_result",
1678             StderrEvent  => "session_run_debug",
1679             CloseEvent   => "session_run_done",
1680             );
1682     $heap->{task}->{ $task->ID } = $task;
1683     return;
1687 sub create_fai_release_db {
1688         my ($table_name, $session_id) = @_;
1689         my $result;
1691     # used for logging
1692     if (not defined $session_id) { $session_id = 0; }
1694     my $ldap_handle = &get_ldap_handle();
1695         if(defined($ldap_handle)) {
1696                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1697                 my $mesg= $ldap_handle->search(
1698                         base   => $ldap_base,
1699                         scope  => 'sub',
1700                         attrs  => [],
1701                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1702                 );
1703                 if($mesg->{'resultCode'} == 0 &&
1704                         $mesg->count != 0) {
1705                         # Walk through all possible FAI container ou's
1706                         my @sql_list;
1707                         my $timestamp= &get_time();
1708                         foreach my $ou (@{$mesg->{entries}}) {
1709                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1710                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1711                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1712                                         if(@tmp_array) {
1713                                                 foreach my $entry (@tmp_array) {
1714                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1715                                                                 my $sql= 
1716                                                                 "INSERT INTO $table_name "
1717                                                                 ."(timestamp, release, class, type, state) VALUES ("
1718                                                                 .$timestamp.","
1719                                                                 ."'".$entry->{'release'}."',"
1720                                                                 ."'".$entry->{'class'}."',"
1721                                                                 ."'".$entry->{'type'}."',"
1722                                                                 ."'".$entry->{'state'}."')";
1723                                                                 push @sql_list, $sql;
1724                                                         }
1725                                                 }
1726                                         }
1727                                 }
1728                         }
1730                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1731                         if(@sql_list) {
1732                                 unshift @sql_list, "DELETE FROM $table_name";
1733                                 $fai_release_db->exec_statementlist(\@sql_list);
1734                         }
1735                         daemon_log("$session_id DEBUG: Done with inserting",6);
1736                 }
1737                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1738         }
1739     $ldap_handle->disconnect;
1740         return $result;
1743 sub get_fai_types {
1744         my $tmp_classes = shift || return undef;
1745         my @result;
1747         foreach my $type(keys %{$tmp_classes}) {
1748                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1749                         my $entry = {
1750                                 type => $type,
1751                                 state => $tmp_classes->{$type}[0],
1752                         };
1753                         push @result, $entry;
1754                 }
1755         }
1757         return @result;
1760 sub get_fai_state {
1761         my $result = "";
1762         my $tmp_classes = shift || return $result;
1764         foreach my $type(keys %{$tmp_classes}) {
1765                 if(defined($tmp_classes->{$type}[0])) {
1766                         $result = $tmp_classes->{$type}[0];
1767                         
1768                 # State is equal for all types in class
1769                         last;
1770                 }
1771         }
1773         return $result;
1776 sub resolve_fai_classes {
1777         my ($fai_base, $ldap_handle) = @_;
1778         my $result;
1779         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1780         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1781         my $fai_classes;
1783         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1784         my $mesg= $ldap_handle->search(
1785                 base   => $fai_base,
1786                 scope  => 'sub',
1787                 attrs  => ['cn','objectClass','FAIstate'],
1788                 filter => $fai_filter,
1789         );
1790         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1792         if($mesg->{'resultCode'} == 0 &&
1793                 $mesg->count != 0) {
1794                 foreach my $entry (@{$mesg->{entries}}) {
1795                         if($entry->exists('cn')) {
1796                                 my $tmp_dn= $entry->dn();
1798                                 # Skip classname and ou dn parts for class
1799                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1801                                 # Skip classes without releases
1802                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1803                                         next;
1804                                 }
1806                                 my $tmp_cn= $entry->get_value('cn');
1807                                 my $tmp_state= $entry->get_value('FAIstate');
1809                                 my $tmp_type;
1810                                 # Get FAI type
1811                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1812                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1813                                                 $tmp_type= $oclass;
1814                                                 last;
1815                                         }
1816                                 }
1818                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1819                                         # A Subrelease
1820                                         my @sub_releases = split(/,/, $tmp_release);
1822                                         # Walk through subreleases and build hash tree
1823                                         my $hash;
1824                                         while(my $tmp_sub_release = pop @sub_releases) {
1825                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1826                                         }
1827                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1828                                 } else {
1829                                         # A branch, no subrelease
1830                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1831                                 }
1832                         } elsif (!$entry->exists('cn')) {
1833                                 my $tmp_dn= $entry->dn();
1834                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1836                                 # Skip classes without releases
1837                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1838                                         next;
1839                                 }
1841                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1842                                         # A Subrelease
1843                                         my @sub_releases= split(/,/, $tmp_release);
1845                                         # Walk through subreleases and build hash tree
1846                                         my $hash;
1847                                         while(my $tmp_sub_release = pop @sub_releases) {
1848                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1849                                         }
1850                                         # Remove the last two characters
1851                                         chop($hash);
1852                                         chop($hash);
1854                                         eval('$fai_classes->'.$hash.'= {}');
1855                                 } else {
1856                                         # A branch, no subrelease
1857                                         if(!exists($fai_classes->{$tmp_release})) {
1858                                                 $fai_classes->{$tmp_release} = {};
1859                                         }
1860                                 }
1861                         }
1862                 }
1864                 # The hash is complete, now we can honor the copy-on-write based missing entries
1865                 foreach my $release (keys %$fai_classes) {
1866                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1867                 }
1868         }
1869         return $result;
1872 sub apply_fai_inheritance {
1873        my $fai_classes = shift || return {};
1874        my $tmp_classes;
1876        # Get the classes from the branch
1877        foreach my $class (keys %{$fai_classes}) {
1878                # Skip subreleases
1879                if($class =~ /^ou=.*$/) {
1880                        next;
1881                } else {
1882                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1883                }
1884        }
1886        # Apply to each subrelease
1887        foreach my $subrelease (keys %{$fai_classes}) {
1888                if($subrelease =~ /ou=/) {
1889                        foreach my $tmp_class (keys %{$tmp_classes}) {
1890                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1891                                        $fai_classes->{$subrelease}->{$tmp_class} =
1892                                        deep_copy($tmp_classes->{$tmp_class});
1893                                } else {
1894                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1895                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1896                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1897                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1898                                                }
1899                                        }
1900                                }
1901                        }
1902                }
1903        }
1905        # Find subreleases in deeper levels
1906        foreach my $subrelease (keys %{$fai_classes}) {
1907                if($subrelease =~ /ou=/) {
1908                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1909                                if($subsubrelease =~ /ou=/) {
1910                                        apply_fai_inheritance($fai_classes->{$subrelease});
1911                                }
1912                        }
1913                }
1914        }
1916        return $fai_classes;
1919 sub get_fai_release_entries {
1920         my $tmp_classes = shift || return;
1921         my $parent = shift || "";
1922         my @result = shift || ();
1924         foreach my $entry (keys %{$tmp_classes}) {
1925                 if(defined($entry)) {
1926                         if($entry =~ /^ou=.*$/) {
1927                                 my $release_name = $entry;
1928                                 $release_name =~ s/ou=//g;
1929                                 if(length($parent)>0) {
1930                                         $release_name = $parent."/".$release_name;
1931                                 }
1932                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1933                                 foreach my $bufentry(@bufentries) {
1934                                         push @result, $bufentry;
1935                                 }
1936                         } else {
1937                                 my @types = get_fai_types($tmp_classes->{$entry});
1938                                 foreach my $type (@types) {
1939                                         push @result, 
1940                                         {
1941                                                 'class' => $entry,
1942                                                 'type' => $type->{'type'},
1943                                                 'release' => $parent,
1944                                                 'state' => $type->{'state'},
1945                                         };
1946                                 }
1947                         }
1948                 }
1949         }
1951         return @result;
1954 sub deep_copy {
1955         my $this = shift;
1956         if (not ref $this) {
1957                 $this;
1958         } elsif (ref $this eq "ARRAY") {
1959                 [map deep_copy($_), @$this];
1960         } elsif (ref $this eq "HASH") {
1961                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1962         } else { die "what type is $_?" }
1966 sub session_run_result {
1967     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1968     $kernel->sig(CHLD => "child_reap");
1971 sub session_run_debug {
1972     my $result = $_[ARG0];
1973     print STDERR "$result\n";
1976 sub session_run_done {
1977     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1978     delete $heap->{task}->{$task_id};
1981 sub create_sources_list {
1982     my $ldap_handle = &get_ldap_handle;
1983         my $result="/tmp/gosa_si_tmp_sources_list";
1985         # Remove old file
1986         if(stat($result)) {
1987                 unlink($result);
1988         }
1990         my $fh;
1991         open($fh, ">$result") or return undef;
1992         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1993                 my $mesg=$ldap_handle->search(
1994                                 base    => $ldap_server_dn,
1995                                 scope   => 'base',
1996                                 attrs   => 'FAIrepository',
1997                                 filter  => 'objectClass=FAIrepositoryServer'
1998                                 );
1999                 if($mesg->count) {
2000                         foreach my $entry(@{$mesg->{'entries'}}) {
2001                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2002                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2003                                         my $line = "deb $server $release";
2004                                         $sections =~ s/,/ /g;
2005                                         $line.= " $sections";
2006                                         print $fh $line."\n";
2007                                 }
2008                         }
2009                 }
2010         }
2011         close($fh);
2013         return $result;
2017 sub run_create_packages_list_db {
2018     my ($session, $heap) = @_[SESSION, HEAP];
2019         my $session_id = $session->ID;
2021         my $task = POE::Wheel::Run->new(
2022                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2023                                         StdoutEvent  => "session_run_result",
2024                                         StderrEvent  => "session_run_debug",
2025                                         CloseEvent   => "session_run_done",
2026                                         );
2027         $heap->{task}->{ $task->ID } = $task;
2031 sub create_packages_list_db {
2032     my ($ldap_handle, $sources_file, $session_id);
2034         if ($packages_list_under_construction) {
2035                 daemon_log("#########################################################################################\n\n\n\n\n");
2036                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait untill this process is finished", 3);
2037                 return;
2038         }
2039         # set packages_list_under_construction to true
2040         $packages_list_under_construction = 1;
2042         if (not defined $session_id) { $session_id = 0; }
2043         if (not defined $ldap_handle) { 
2044                 $ldap_handle= &get_ldap_handle();
2046                 if (not defined $ldap_handle) {
2047                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2048                         return;
2049                 }
2050         }
2051     if (not defined $sources_file) { 
2052         $sources_file = &create_sources_list;
2053     }
2055     my $line;
2056     daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2058     open(CONFIG, "<$sources_file") or do {
2059         daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2060         return;
2061     };
2062     
2063     # Read lines
2064     while ($line = <CONFIG>){
2065         # Unify
2066         chop($line);
2067         $line =~ s/^\s+//;
2068         $line =~ s/^\s+/ /;
2070         # Strip comments
2071         $line =~ s/#.*$//g;
2073         # Skip empty lines
2074         if ($line =~ /^\s*$/){
2075             next;
2076         }
2078         # Interpret deb line
2079         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2080             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2081             my $section;
2082             foreach $section (split(' ', $sections)){
2083                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2084             }
2085         }
2086     }
2088     close (CONFIG);
2090     daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2091         # set packages_list_under_construction to false
2092         $packages_list_under_construction = 0;
2094     return;
2098 sub parse_package_info {
2099   my ($baseurl, $dist, $section, $session_id)= @_;
2100   my ($package);
2101         if (not defined $session_id) { $session_id = 0; }
2102   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2103   $repo_dirs{ "${repo_path}/pool" } = 1;
2105   foreach $package ("Packages.gz"){
2106     daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2107     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2108     parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2109   }
2110   find(\&cleanup_and_extract, keys( %repo_dirs ));
2114 sub get_package {
2115   my ($url, $dest, $session_id)= @_;
2116   if (not defined $session_id) { $session_id = 0; }
2118   my $tpath = dirname($dest);
2119   -d "$tpath" || mkpath "$tpath";
2121   # This is ugly, but I've no time to take a look at "how it works in perl"
2122   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2123       system("gunzip -cd '$dest' > '$dest.in'");
2124           daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2125 #      unlink($dest);
2126           daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2127   } else {
2128       daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2129   }
2130   return 0;
2133 sub parse_package {
2134     my ($path, $dist, $srv_path, $session_id)= @_;
2135         if (not defined $session_id) { $session_id = 0;}
2136     my ($package, $version, $section, $description);
2137     my @sql_list;
2138     my $PACKAGES;
2140     if(not stat("$path.in")) {
2141         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2142         return;
2143     }
2145     open($PACKAGES, "<$path.in");
2146         if(not defined($PACKAGES)) {
2147         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2148         return;
2149     }
2151     # Read lines
2152     while (<$PACKAGES>){
2153         my $line = $_;
2154         # Unify
2155         chop($line);
2157         # Use empty lines as a trigger
2158         if ($line =~ /^\s*$/){
2159             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2160             push(@sql_list, $sql);
2161             $package = "none";
2162             $version = "none";
2163             $section = "none";
2164             $description = "none"; 
2165             next;
2166         }
2168         # Trigger for package name
2169         if ($line =~ /^Package:\s/){
2170             ($package)= ($line =~ /^Package: (.*)$/);
2171             next;
2172         }
2174         # Trigger for version
2175         if ($line =~ /^Version:\s/){
2176             ($version)= ($line =~ /^Version: (.*)$/);
2177             next;
2178         }
2180         # Trigger for description
2181         if ($line =~ /^Description:\s/){
2182             ($description)= ($line =~ /^Description: (.*)$/);
2183             next;
2184         }
2186         # Trigger for section
2187         if ($line =~ /^Section:\s/){
2188             ($section)= ($line =~ /^Section: (.*)$/);
2189             next;
2190         }
2192         # Trigger for filename
2193         if ($line =~ /^Filename:\s/){
2194                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2195                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2196                 next;
2197         }
2198     }
2200     close( $PACKAGES );
2201 #    unlink( "$path.in" );
2202     
2203     $packages_list_db->exec_statementlist(\@sql_list);
2206 sub store_fileinfo {
2207   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2209   my %fileinfo = (
2210     'package' => $package,
2211     'dist' => $dist,
2212     'version' => $vers,
2213   );
2215   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2218 sub cleanup_and_extract {
2219         my ($session_id) = @_;
2220         if (not defined $session_id) { $session_id  = 0; }              
2221                 my $fileinfo = $repo_files{ $File::Find::name };
2223                 if( defined $fileinfo ) {
2225                                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2226                                 my $sql;
2227                                 my $package = $fileinfo->{ 'package' };
2228                                 my $newver = $fileinfo->{ 'version' };
2230                                 mkpath($dir);
2231                                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2233                                 if( -f "$dir/DEBIAN/templates" ) {
2235                                                 daemon_log("$session_id DEBUG: Found debconf templates in '$package' - $newver", 5);
2237                                                 my $tmpl= "";
2238                                                 {
2239                                                                 local $/=undef;
2240                                                                 open FILE, "$dir/DEBIAN/templates";
2241                                                                 $tmpl = &encode_base64(<FILE>);
2242                                                                 close FILE;
2243                                                 }
2244                                                 rmtree("$dir/DEBIAN/templates");
2246                                                 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2248                                 } else {
2249                                                 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2250                                 }
2252                                 my $res= $main::packages_list_db->update_dbentry($sql);
2253                 }
2257 #==== MAIN = main ==============================================================
2258 #  parse commandline options
2259 Getopt::Long::Configure( "bundling" );
2260 GetOptions("h|help" => \&usage,
2261         "c|config=s" => \$cfg_file,
2262         "f|foreground" => \$foreground,
2263         "v|verbose+" => \$verbose,
2264         "no-bus+" => \$no_bus,
2265         "no-arp+" => \$no_arp,
2266            );
2268 #  read and set config parameters
2269 &check_cmdline_param ;
2270 &read_configfile;
2271 &check_pid;
2273 $SIG{CHLD} = 'IGNORE';
2275 # forward error messages to logfile
2276 if( ! $foreground ) {
2277   open( STDIN,  '+>/dev/null' );
2278   open( STDOUT, '+>&STDIN'    );
2279   open( STDERR, '+>&STDIN'    );
2282 # Just fork, if we are not in foreground mode
2283 if( ! $foreground ) { 
2284     chdir '/'                 or die "Can't chdir to /: $!";
2285     $pid = fork;
2286     setsid                    or die "Can't start a new session: $!";
2287     umask 0;
2288 } else { 
2289     $pid = $$; 
2292 # Do something useful - put our PID into the pid_file
2293 if( 0 != $pid ) {
2294     open( LOCK_FILE, ">$pid_file" );
2295     print LOCK_FILE "$pid\n";
2296     close( LOCK_FILE );
2297     if( !$foreground ) { 
2298         exit( 0 ) 
2299     };
2302 daemon_log(" ", 1);
2303 daemon_log("$0 started!", 1);
2305 if ($no_bus > 0) {
2306     $bus_activ = "false"
2309 # connect to gosa-si job queue
2310 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2311 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2313 # connect to known_clients_db
2314 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2315 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2317 # connect to known_server_db
2318 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2319 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2321 # connect to login_usr_db
2322 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2323 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2325 # connect to fai_server_db and fai_release_db
2326 unlink($fai_server_file_name);
2327 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2328 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2330 unlink($fai_release_file_name);
2331 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2332 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2334 # connect to packages_list_db
2335 unlink($packages_list_file_name);
2336 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2337 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2339 # connect to messaging_db
2340 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2341 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2344 # create xml object used for en/decrypting
2345 $xml = new XML::Simple();
2347 # create socket for incoming xml messages
2349 POE::Component::Server::TCP->new(
2350         Port => $server_port,
2351         ClientInput => sub {
2352         my ($kernel, $input) = @_[KERNEL, ARG0];
2353         push(@tasks, $input);
2354         $kernel->yield("next_task");
2355         },
2356     InlineStates => {
2357         next_task => \&next_task,
2358         task_result => \&handle_task_result,
2359         task_done   => \&handle_task_done,
2360         task_debug  => \&handle_task_debug,
2361         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2362     }
2363 );
2365 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2367 # create session for repeatedly checking the job queue for jobs
2368 POE::Session->create(
2369         inline_states => {
2370                 _start => \&_start,
2371                 sig_handler => \&sig_handler,
2372         watch_for_new_messages => \&watch_for_new_messages,
2373         watch_for_done_messages => \&watch_for_done_messages,
2374                 watch_for_new_jobs => \&watch_for_new_jobs,
2375         watch_for_done_jobs => \&watch_for_done_jobs,
2376         create_packages_list_db => \&run_create_packages_list_db,
2377         create_fai_server_db => \&run_create_fai_server_db,
2378         create_fai_release_db => \&run_create_fai_release_db,
2379         session_run_result => \&session_run_result,
2380         session_run_debug => \&session_run_debug,
2381         session_run_done => \&session_run_done,
2382         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2383         }
2384 );
2387 # import all modules
2388 &import_modules;
2390 # check wether all modules are gosa-si valid passwd check
2392 POE::Kernel->run();
2393 exit;