Code

Updated C&P for FAI.
[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";
110 $no_arp = 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                 );
129 # holds all other gosa-sd as well as the gosa-sd-bus
130 our $known_server_db;
131 our $known_server_tn = "known_server";
132 my $known_server_file_name;
133 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
135 # holds all registrated clients
136 our $known_clients_db;
137 our $known_clients_tn = "known_clients";
138 my $known_clients_file_name;
139 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
141 # holds all logged in user at each client 
142 our $login_users_db;
143 our $login_users_tn = "login_users";
144 my $login_users_file_name;
145 my @login_users_col_names = ('client', 'user', 'timestamp');
147 # holds all fai server, the debian release and tag
148 our $fai_server_db;
149 our $fai_server_tn = "fai_server"; 
150 my $fai_server_file_name;
151 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag'); 
152 our $fai_release_tn = "fai_release"; 
153 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state'); 
155 # holds all packages available from different repositories
156 our $packages_list_db;
157 our $packages_list_tn = "packages_list";
158 my $packages_list_file_name;
159 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
160 my $outdir = "/tmp/packages_list_db";
161 my $arch = "i386"; 
163 # holds all messages which should be delivered to a user
164 our $messaging_db;
165 our $messaging_tn = "messaging"; 
166 our @messaging_col_names = ('subject', 'message_from', 'message_to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER' );
167 my $messaging_file_name;
169 # path to directory to store client install log files
170 our $client_fai_log_dir = "/var/log/fai"; 
172 # queue which stores taskes until one of the $max_children children are ready to process the task
173 my @tasks = qw();
174 my $max_children = 2;
177 %cfg_defaults = (
178 "general" => {
179     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
180     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
181     },
182 "bus" => {
183     "activ" => [\$bus_activ, "true"],
184     },
185 "server" => {
186     "port" => [\$server_port, "20081"],
187     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
188     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
189     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
190     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
191     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
192     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
193     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
194     "repo-path" => [\$repo_path, '/srv/www/repository'],
195     "ldap-uri" => [\$ldap_uri, ""],
196     "ldap-base" => [\$ldap_base, ""],
197     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
198     "ldap-admin-password" => [\$ldap_admin_password, ""],
199     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
200     "max-clients" => [\$max_clients, 10],
201     },
202 "GOsaPackages" => {
203     "ip" => [\$gosa_ip, "0.0.0.0"],
204     "port" => [\$gosa_port, "20082"],
205     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
206     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
207     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
208     "key" => [\$GosaPackages_key, "none"],
209     },
210 "SIPackages" => {
211     "key" => [\$SIPackages_key, "none"],
212     },
213 );
216 #===  FUNCTION  ================================================================
217 #         NAME:  usage
218 #   PARAMETERS:  nothing
219 #      RETURNS:  nothing
220 #  DESCRIPTION:  print out usage text to STDERR
221 #===============================================================================
222 sub usage {
223     print STDERR << "EOF" ;
224 usage: $prg [-hvf] [-c config]
226            -h        : this (help) message
227            -c <file> : config file
228            -f        : foreground, process will not be forked to background
229            -v        : be verbose (multiple to increase verbosity)
230            -no-bus   : starts $prg without connection to bus
231            -no-arp   : starts $prg without connection to arp module
232  
233 EOF
234     print "\n" ;
238 #===  FUNCTION  ================================================================
239 #         NAME:  read_configfile
240 #   PARAMETERS:  cfg_file - string -
241 #      RETURNS:  nothing
242 #  DESCRIPTION:  read cfg_file and set variables
243 #===============================================================================
244 sub read_configfile {
245     my $cfg;
246     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
247         if( -r $cfg_file ) {
248             $cfg = Config::IniFiles->new( -file => $cfg_file );
249         } else {
250             print STDERR "Couldn't read config file!\n";
251         }
252     } else {
253         $cfg = Config::IniFiles->new() ;
254     }
255     foreach my $section (keys %cfg_defaults) {
256         foreach my $param (keys %{$cfg_defaults{ $section }}) {
257             my $pinfo = $cfg_defaults{ $section }{ $param };
258             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
259         }
260     }
264 #===  FUNCTION  ================================================================
265 #         NAME:  logging
266 #   PARAMETERS:  level - string - default 'info'
267 #                msg - string -
268 #                facility - string - default 'LOG_DAEMON'
269 #      RETURNS:  nothing
270 #  DESCRIPTION:  function for logging
271 #===============================================================================
272 sub daemon_log {
273     # log into log_file
274     my( $msg, $level ) = @_;
275     if(not defined $msg) { return }
276     if(not defined $level) { $level = 1 }
277     if(defined $log_file){
278         open(LOG_HANDLE, ">>$log_file");
279         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
280             print STDERR "cannot open $log_file: $!";
281             return }
282             chomp($msg);
283             if($level <= $verbose){
284                 my ($seconds, $minutes, $hours, $monthday, $month,
285                         $year, $weekday, $yearday, $sommertime) = localtime(time);
286                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
287                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
288                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
289                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
290                 $month = $monthnames[$month];
291                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
292                 $year+=1900;
293                 my $name = $prg;
295                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
296                 print LOG_HANDLE $log_msg;
297                 if( $foreground ) { 
298                     print STDERR $log_msg;
299                 }
300             }
301         close( LOG_HANDLE );
302     }
306 #===  FUNCTION  ================================================================
307 #         NAME:  check_cmdline_param
308 #   PARAMETERS:  nothing
309 #      RETURNS:  nothing
310 #  DESCRIPTION:  validates commandline parameter
311 #===============================================================================
312 sub check_cmdline_param () {
313     my $err_config;
314     my $err_counter = 0;
315         if(not defined($cfg_file)) {
316                 $cfg_file = "/etc/gosa-si/server.conf";
317                 if(! -r $cfg_file) {
318                         $err_config = "please specify a config file";
319                         $err_counter += 1;
320                 }
321     }
322     if( $err_counter > 0 ) {
323         &usage( "", 1 );
324         if( defined( $err_config)) { print STDERR "$err_config\n"}
325         print STDERR "\n";
326         exit( -1 );
327     }
331 #===  FUNCTION  ================================================================
332 #         NAME:  check_pid
333 #   PARAMETERS:  nothing
334 #      RETURNS:  nothing
335 #  DESCRIPTION:  handels pid processing
336 #===============================================================================
337 sub check_pid {
338     $pid = -1;
339     # Check, if we are already running
340     if( open(LOCK_FILE, "<$pid_file") ) {
341         $pid = <LOCK_FILE>;
342         if( defined $pid ) {
343             chomp( $pid );
344             if( -f "/proc/$pid/stat" ) {
345                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
346                 if( $stat ) {
347                                         daemon_log("ERROR: Already running",1);
348                     close( LOCK_FILE );
349                     exit -1;
350                 }
351             }
352         }
353         close( LOCK_FILE );
354         unlink( $pid_file );
355     }
357     # create a syslog msg if it is not to possible to open PID file
358     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
359         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
360         if (open(LOCK_FILE, '<', $pid_file)
361                 && ($pid = <LOCK_FILE>))
362         {
363             chomp($pid);
364             $msg .= "(PID $pid)\n";
365         } else {
366             $msg .= "(unable to read PID)\n";
367         }
368         if( ! ($foreground) ) {
369             openlog( $0, "cons,pid", "daemon" );
370             syslog( "warning", $msg );
371             closelog();
372         }
373         else {
374             print( STDERR " $msg " );
375         }
376         exit( -1 );
377     }
380 #===  FUNCTION  ================================================================
381 #         NAME:  import_modules
382 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
383 #                are stored
384 #      RETURNS:  nothing
385 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
386 #                state is on is imported by "require 'file';"
387 #===============================================================================
388 sub import_modules {
389     daemon_log(" ", 1);
391     if (not -e $modules_path) {
392         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
393     }
395     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
396     while (defined (my $file = readdir (DIR))) {
397         if (not $file =~ /(\S*?).pm$/) {
398             next;
399         }
400                 my $mod_name = $1;
402         if( $file =~ /ArpHandler.pm/ ) {
403             if( $no_arp > 0 ) {
404                 next;
405             }
406         }
407         
408         eval { require $file; };
409         if ($@) {
410             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
411             daemon_log("$@", 5);
412                 } else {
413                         my $info = eval($mod_name.'::get_module_info()');
414                         # Only load module if get_module_info() returns a non-null object
415                         if( $info ) {
416                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
417                                 $known_modules->{$mod_name} = $info;
418                                 daemon_log("INFO: module $mod_name loaded", 5);
419                         }
420                 }
421     }   
422     close (DIR);
426 #===  FUNCTION  ================================================================
427 #         NAME:  sig_int_handler
428 #   PARAMETERS:  signal - string - signal arose from system
429 #      RETURNS:  noting
430 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
431 #===============================================================================
432 sub sig_int_handler {
433     my ($signal) = @_;
435 #       if (defined($ldap_handle)) {
436 #               $ldap_handle->disconnect;
437 #       }
438     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
439     
441     daemon_log("shutting down gosa-si-server", 1);
442     system("kill `ps -C gosa-si-server -o pid=`");
444 $SIG{INT} = \&sig_int_handler;
447 sub check_key_and_xml_validity {
448     my ($crypted_msg, $module_key, $session_id) = @_;
449     my $msg;
450     my $msg_hash;
451     my $error_string;
452     eval{
453         $msg = &decrypt_msg($crypted_msg, $module_key);
455         if ($msg =~ /<xml>/i){
456             $msg =~ s/\s+/ /g;  # just for better daemon_log
457             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
458             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
460             ##############
461             # check header
462             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
463             my $header_l = $msg_hash->{'header'};
464             if( 1 > @{$header_l} ) { die 'empty header tag'; }
465             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
466             my $header = @{$header_l}[0];
467             if( 0 == length $header) { die 'empty string in header tag'; }
469             ##############
470             # check source
471             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
472             my $source_l = $msg_hash->{'source'};
473             if( 1 > @{$source_l} ) { die 'empty source tag'; }
474             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
475             my $source = @{$source_l}[0];
476             if( 0 == length $source) { die 'source error'; }
478             ##############
479             # check target
480             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
481             my $target_l = $msg_hash->{'target'};
482             if( 1 > @{$target_l} ) { die 'empty target tag'; }
483         }
484     };
485     if($@) {
486         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
487         $msg = undef;
488         $msg_hash = undef;
489     }
491     return ($msg, $msg_hash);
495 sub check_outgoing_xml_validity {
496     my ($msg) = @_;
498     my $msg_hash;
499     eval{
500         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
502         ##############
503         # check header
504         my $header_l = $msg_hash->{'header'};
505         if( 1 != @{$header_l} ) {
506             die 'no or more than one headers specified';
507         }
508         my $header = @{$header_l}[0];
509         if( 0 == length $header) {
510             die 'header has length 0';
511         }
513         ##############
514         # check source
515         my $source_l = $msg_hash->{'source'};
516         if( 1 != @{$source_l} ) {
517             die 'no or more than 1 sources specified';
518         }
519         my $source = @{$source_l}[0];
520         if( 0 == length $source) {
521             die 'source has length 0';
522         }
523         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
524                 $source =~ /^GOSA$/i ) {
525             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
526         }
527         
528         ##############
529         # check target  
530         my $target_l = $msg_hash->{'target'};
531         if( 0 == @{$target_l} ) {
532             die "no targets specified";
533         }
534         foreach my $target (@$target_l) {
535             if( 0 == length $target) {
536                 die "target has length 0";
537             }
538             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
539                     $target =~ /^GOSA$/i ||
540                     $target =~ /^\*$/ ||
541                     $target =~ /KNOWN_SERVER/i ||
542                     $target =~ /JOBDB/i ||
543                     $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 ){
544                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
545             }
546         }
547     };
548     if($@) {
549         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
550         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
551         $msg_hash = undef;
552     }
554     return ($msg_hash);
558 sub input_from_known_server {
559     my ($input, $remote_ip, $session_id) = @_ ;  
560     my ($msg, $msg_hash, $module);
562     my $sql_statement= "SELECT * FROM known_server";
563     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
565     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
566         my $host_name = $hit->{hostname};
567         if( not $host_name =~ "^$remote_ip") {
568             next;
569         }
570         my $host_key = $hit->{hostkey};
571         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
572         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
574         # check if module can open msg envelope with module key
575         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
576         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
577             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
578             daemon_log("$@", 8);
579             next;
580         }
581         else {
582             $msg = $tmp_msg;
583             $msg_hash = $tmp_msg_hash;
584             $module = "SIPackages";
585             last;
586         }
587     }
589     if( (!$msg) || (!$msg_hash) || (!$module) ) {
590         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
591     }
592   
593     return ($msg, $msg_hash, $module);
597 sub input_from_known_client {
598     my ($input, $remote_ip, $session_id) = @_ ;  
599     my ($msg, $msg_hash, $module);
601     my $sql_statement= "SELECT * FROM known_clients";
602     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
603     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
604         my $host_name = $hit->{hostname};
605         if( not $host_name =~ /^$remote_ip:\d*$/) {
606                 next;
607                 }
608         my $host_key = $hit->{hostkey};
609         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
610         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
612         # check if module can open msg envelope with module key
613         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
615         if( (!$msg) || (!$msg_hash) ) {
616             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
617             &daemon_log("$@", 8);
618             next;
619         }
620         else {
621             $module = "SIPackages";
622             last;
623         }
624     }
626     if( (!$msg) || (!$msg_hash) || (!$module) ) {
627         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
628     }
630     return ($msg, $msg_hash, $module);
634 sub input_from_unknown_host {
635     no strict "refs";
636     my ($input, $session_id) = @_ ;
637     my ($msg, $msg_hash, $module);
638     my $error_string;
639     
640         my %act_modules = %$known_modules;
642         while( my ($mod, $info) = each(%act_modules)) {
644         # check a key exists for this module
645         my $module_key = ${$mod."_key"};
646         if( not defined $module_key ) {
647             if( $mod eq 'ArpHandler' ) {
648                 next;
649             }
650             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
651             next;
652         }
653         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
655         # check if module can open msg envelope with module key
656         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
657         if( (not defined $msg) || (not defined $msg_hash) ) {
658             next;
659         }
660         else {
661             $module = $mod;
662             last;
663         }
664     }
666     if( (!$msg) || (!$msg_hash) || (!$module)) {
667         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
668     }
670     return ($msg, $msg_hash, $module);
674 sub create_ciphering {
675     my ($passwd) = @_;
676         if((!defined($passwd)) || length($passwd)==0) {
677                 $passwd = "";
678         }
679     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
680     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
681     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
682     $my_cipher->set_iv($iv);
683     return $my_cipher;
687 sub encrypt_msg {
688     my ($msg, $key) = @_;
689     my $my_cipher = &create_ciphering($key);
690     my $len;
691     {
692             use bytes;
693             $len= 16-length($msg)%16;
694     }
695     $msg = "\0"x($len).$msg;
696     $msg = $my_cipher->encrypt($msg);
697     chomp($msg = &encode_base64($msg));
698     # there are no newlines allowed inside msg
699     $msg=~ s/\n//g;
700     return $msg;
704 sub decrypt_msg {
706     my ($msg, $key) = @_ ;
707     $msg = &decode_base64($msg);
708     my $my_cipher = &create_ciphering($key);
709     $msg = $my_cipher->decrypt($msg); 
710     $msg =~ s/\0*//g;
711     return $msg;
715 sub get_encrypt_key {
716     my ($target) = @_ ;
717     my $encrypt_key;
718     my $error = 0;
720     # target can be in known_server
721     if( not defined $encrypt_key ) {
722         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
723         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
724         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
725             my $host_name = $hit->{hostname};
726             if( $host_name ne $target ) {
727                 next;
728             }
729             $encrypt_key = $hit->{hostkey};
730             last;
731         }
732     }
734     # target can be in known_client
735     if( not defined $encrypt_key ) {
736         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
737         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
738         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
739             my $host_name = $hit->{hostname};
740             if( $host_name ne $target ) {
741                 next;
742             }
743             $encrypt_key = $hit->{hostkey};
744             last;
745         }
746     }
748     return $encrypt_key;
752 #===  FUNCTION  ================================================================
753 #         NAME:  open_socket
754 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
755 #                [PeerPort] string necessary if port not appended by PeerAddr
756 #      RETURNS:  socket IO::Socket::INET
757 #  DESCRIPTION:  open a socket to PeerAddr
758 #===============================================================================
759 sub open_socket {
760     my ($PeerAddr, $PeerPort) = @_ ;
761     if(defined($PeerPort)){
762         $PeerAddr = $PeerAddr.":".$PeerPort;
763     }
764     my $socket;
765     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
766             Porto => "tcp",
767             Type => SOCK_STREAM,
768             Timeout => 5,
769             );
770     if(not defined $socket) {
771         return;
772     }
773 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
774     return $socket;
778 #===  FUNCTION  ================================================================
779 #         NAME:  get_ip 
780 #   PARAMETERS:  interface name (i.e. eth0)
781 #      RETURNS:  (ip address) 
782 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
783 #===============================================================================
784 sub get_ip {
785         my $ifreq= shift;
786         my $result= "";
787         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
788         my $proto= getprotobyname('ip');
790         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
791                 or die "socket: $!";
793         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
794                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
795                 my ($port, $addr) = sockaddr_in $sin;
796                 my $ip            = inet_ntoa $addr;
798                 if ($ip && length($ip) > 0) {
799                         $result = $ip;
800                 }
801         }
803         return $result;
807 sub get_local_ip_for_remote_ip {
808         my $remote_ip= shift;
809         my $result="0.0.0.0";
811         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
812                 if($remote_ip eq "127.0.0.1") {
813                         $result = "127.0.0.1";
814                 } else {
815                         my $PROC_NET_ROUTE= ('/proc/net/route');
817                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
818                                 or die "Could not open $PROC_NET_ROUTE";
820                         my @ifs = <PROC_NET_ROUTE>;
822                         close(PROC_NET_ROUTE);
824                         # Eat header line
825                         shift @ifs;
826                         chomp @ifs;
827                         foreach my $line(@ifs) {
828                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
829                                 my $destination;
830                                 my $mask;
831                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
832                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
833                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
834                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
835                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
836                                         # destination matches route, save mac and exit
837                                         $result= &get_ip($Iface);
838                                         last;
839                                 }
840                         }
841                 }
842         } else {
843                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
844         }
845         return $result;
849 sub send_msg_to_target {
850     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
851     my $error = 0;
852     my $header;
853     my $new_status;
854     my $act_status;
855     my ($sql_statement, $res);
856   
857     if( $msg_header ) {
858         $header = "'$msg_header'-";
859     } else {
860         $header = "";
861     }
863         # Patch the source ip
864         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
865                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
866                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
867         }
869     # encrypt xml msg
870     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
872     # opensocket
873     my $socket = &open_socket($address);
874     if( !$socket ) {
875         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
876         $error++;
877     }
878     
879     if( $error == 0 ) {
880         # send xml msg
881         print $socket $crypted_msg."\n";
883         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
884         #daemon_log("DEBUG: message:\n$msg", 9);
885         
886     }
888     # close socket in any case
889     if( $socket ) {
890         close $socket;
891     }
893     if( $error > 0 ) { $new_status = "down"; }
894     else { $new_status = $msg_header; }
897     # known_clients
898     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
899     $res = $known_clients_db->select_dbentry($sql_statement);
900     if( keys(%$res) > 0) {
901         $act_status = $res->{1}->{'status'};
902         if( $act_status eq "down" ) {
903             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
904             $res = $known_clients_db->del_dbentry($sql_statement);
905             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
906         } else { 
907             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
908             $res = $known_clients_db->update_dbentry($sql_statement);
909             if($new_status eq "down"){
910                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
911             } else {
912                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
913             }
914         }
915     }
917     # known_server
918     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
919     $res = $known_server_db->select_dbentry($sql_statement);
920     if( keys(%$res) > 0 ) {
921         $act_status = $res->{1}->{'status'};
922         if( $act_status eq "down" ) {
923             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
924             $res = $known_server_db->del_dbentry($sql_statement);
925             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
926         } 
927         else { 
928             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
929             $res = $known_server_db->update_dbentry($sql_statement);
930             if($new_status eq "down"){
931                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
932             }
933             else {
934                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
935             }
936         }
937     }
938     return $error; 
942 sub update_jobdb_status_for_send_msgs {
943     my ($answer, $error) = @_;
944     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
945         my $jobdb_id = $1;
946             
947         # sending msg faild
948         if( $error ) {
949             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
950                 my $sql_statement = "UPDATE $job_queue_tn ".
951                     "SET status='error', result='can not deliver msg, please consult log file' ".
952                     "WHERE id='$jobdb_id'";
953                 my $res = $job_db->update_dbentry($sql_statement);
954             }
956         # sending msg was successful
957         } else {
958             my $sql_statement = "UPDATE $job_queue_tn ".
959                 "SET status='done' ".
960                 "WHERE id='$jobdb_id' AND status='processed'";
961             my $res = $job_db->update_dbentry($sql_statement);
962         }
963     }
966 sub _start {
967     my ($kernel) = $_[KERNEL];
968     &trigger_db_loop($kernel);
969     $global_kernel = $kernel;
970         $kernel->yield('create_fai_server_db', $fai_server_tn );
971         $kernel->yield('create_fai_release_db', $fai_release_tn );
972         $kernel->sig(USR1 => "sig_handler");
973         $kernel->sig(USR2 => "create_packages_list_db");
976 sub sig_handler {
977         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
978         daemon_log("0 INFO got signal '$signal'", 1); 
979         $kernel->sig_handled();
980         return;
983 sub next_task {
984     my ($session, $heap) = @_[SESSION, HEAP];
986     while ( keys( %{ $heap->{task} } ) < $max_children ) {
987         my $next_task = shift @tasks;
988         last unless defined $next_task;
990         my $task = POE::Wheel::Run->new(
991                 Program => sub { process_task($session, $heap, $next_task) },
992                 StdioFilter => POE::Filter::Reference->new(),
993                 StdoutEvent  => "task_result",
994                 StderrEvent  => "task_debug",
995                 CloseEvent   => "task_done",
996                );
998         $heap->{task}->{ $task->ID } = $task;
999     }
1002 sub handle_task_result {
1003     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1004     my $client_answer = $result->{'answer'};
1005     if( $client_answer =~ s/session_id=(\d+)$// ) {
1006         my $session_id = $1;
1007         if( defined $session_id ) {
1008             my $session_reference = $kernel->ID_id_to_session($session_id);
1009             if( defined $session_reference ) {
1010                 $heap = $session_reference->get_heap();
1011             }
1012         }
1014         if(exists $heap->{'client'}) {
1015             $heap->{'client'}->put($client_answer);
1016         }
1017     }
1018     $kernel->sig(CHLD => "child_reap");
1021 sub handle_task_debug {
1022     my $result = $_[ARG0];
1023     print STDERR "$result\n";
1026 sub handle_task_done {
1027     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1028     delete $heap->{task}->{$task_id};
1029     $kernel->yield("next_task");
1032 sub process_task {
1033     no strict "refs";
1034     my ($session, $heap, $input) = @_;
1035     my $session_id = $session->ID;
1036     my ($msg, $msg_hash, $module);
1037     my $error = 0;
1038     my $answer_l;
1039     my ($answer_header, @answer_target_l, $answer_source);
1040     my $client_answer = "";
1042     daemon_log("", 5); 
1043     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1044     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1046     ####################
1047     # check incoming msg
1048     # msg is from a new client or gosa
1049     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1050     # msg is from a gosa-si-server or gosa-si-bus
1051     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1052         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1053     }
1054     # msg is from a gosa-si-client
1055     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1056         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1057     }
1058     # an error occurred
1059     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1060         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1061         # could not understand a msg from its server the client cause a re-registering process
1062         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);
1063         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1064         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1065         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1066             my $host_name = $hit->{'hostname'};
1067             my $host_key = $hit->{'hostkey'};
1068             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1069             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1070             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1071         }
1072         $error++;
1073     }
1075     ######################
1076     # process incoming msg
1077     if( $error == 0) {
1078         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1079                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1080         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1081         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1083         if ( 0 < @{$answer_l} ) {
1084             my $answer_str = join("\n", @{$answer_l});
1085             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1086         }
1087     }
1088     if( !$answer_l ) { $error++ };
1090     ########
1091     # answer
1092     if( $error == 0 ) {
1094         foreach my $answer ( @{$answer_l} ) {
1095             # for each answer in answer list
1096             
1097             # check outgoing msg to xml validity
1098             my $answer_hash = &check_outgoing_xml_validity($answer);
1099             if( not defined $answer_hash ) {
1100                 next;
1101             }
1102             
1103             $answer_header = @{$answer_hash->{'header'}}[0];
1104             @answer_target_l = @{$answer_hash->{'target'}};
1105             $answer_source = @{$answer_hash->{'source'}}[0];
1107             # deliver msg to all targets 
1108             foreach my $answer_target ( @answer_target_l ) {
1110                 # targets of msg are all gosa-si-clients in known_clients_db
1111                 if( $answer_target eq "*" ) {
1112                     # answer is for all clients
1113                     my $sql_statement= "SELECT * FROM known_clients";
1114                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1115                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1116                         my $host_name = $hit->{hostname};
1117                         my $host_key = $hit->{hostkey};
1118                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1119                         &update_jobdb_status_for_send_msgs($answer, $error);
1120                     }
1121                 }
1123                 # targets of msg are all gosa-si-server in known_server_db
1124                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1125                     # answer is for all server in known_server
1126                     my $sql_statement= "SELECT * FROM known_server";
1127                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1128                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1129                         my $host_name = $hit->{hostname};
1130                         my $host_key = $hit->{hostkey};
1131                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1132                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1133                         &update_jobdb_status_for_send_msgs($answer, $error);
1134                     }
1135                 }
1137                 # target of msg is GOsa
1138                                 elsif( $answer_target eq "GOSA" ) {
1139                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1140                                         my $add_on = "";
1141                     if( defined $session_id ) {
1142                         $add_on = ".session_id=$session_id";
1143                     }
1144                     # answer is for GOSA and has to returned to connected client
1145                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1146                     $client_answer = $gosa_answer.$add_on;
1147                 }
1149                 # target of msg is job queue at this host
1150                 elsif( $answer_target eq "JOBDB") {
1151                     $answer =~ /<header>(\S+)<\/header>/;   
1152                     my $header;
1153                     if( defined $1 ) { $header = $1; }
1154                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1155                     &update_jobdb_status_for_send_msgs($answer, $error);
1156                 }
1158                 # target of msg is a mac address
1159                 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 ) {
1160                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1161                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1162                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1163                     my $found_ip_flag = 0;
1164                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1165                         my $host_name = $hit->{hostname};
1166                         my $host_key = $hit->{hostkey};
1167                         $answer =~ s/$answer_target/$host_name/g;
1168                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1169                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1170                         &update_jobdb_status_for_send_msgs($answer, $error);
1171                         $found_ip_flag++ ;
1172                     }   
1173                     if( $found_ip_flag == 0) {
1174                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1175                         if( $bus_activ eq "true" ) { 
1176                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1177                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1178                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1179                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1180                                 my $bus_address = $hit->{hostname};
1181                                 my $bus_key = $hit->{hostkey};
1182                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1183                                 &update_jobdb_status_for_send_msgs($answer, $error);
1184                                 last;
1185                             }
1186                         }
1188                     }
1190                 #  answer is for one specific host   
1191                 } else {
1192                     # get encrypt_key
1193                     my $encrypt_key = &get_encrypt_key($answer_target);
1194                     if( not defined $encrypt_key ) {
1195                         # unknown target, forward msg to bus
1196                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1197                         if( $bus_activ eq "true" ) { 
1198                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1199                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1200                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1201                             my $res_length = keys( %{$query_res} );
1202                             if( $res_length == 0 ){
1203                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1204                                         "no bus found in known_server", 3);
1205                             }
1206                             else {
1207                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1208                                     my $bus_key = $hit->{hostkey};
1209                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1210                                     &update_jobdb_status_for_send_msgs($answer, $error);
1211                                 }
1212                             }
1213                         }
1214                         next;
1215                     }
1216                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1217                     &update_jobdb_status_for_send_msgs($answer, $error);
1218                 }
1219             }
1220         }
1221     }
1223     my $filter = POE::Filter::Reference->new();
1224     my %result = ( 
1225             status => "seems ok to me",
1226             answer => $client_answer,
1227             );
1229     my $output = $filter->put( [ \%result ] );
1230     print @$output;
1236 sub trigger_db_loop {
1237         my ($kernel) = @_ ;
1238         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1239         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1240     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1241     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1244 sub watch_for_done_jobs {
1245     my ($kernel,$heap) = @_[KERNEL, HEAP];
1247     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1248         " WHERE status='done'";
1249         my $res = $job_db->select_dbentry( $sql_statement );
1251     while( my ($id, $hit) = each %{$res} ) {
1252         my $jobdb_id = $hit->{id};
1253         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1254         my $res = $job_db->del_dbentry($sql_statement);
1255     }
1257     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1260 sub watch_for_new_jobs {
1261         my ($kernel,$heap) = @_[KERNEL, HEAP];
1263         # check gosa job queue for jobs with executable timestamp
1264         my $timestamp = &get_time();
1265         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1266         my $res = $job_db->exec_statement( $sql_statement );
1268         # Merge all new jobs that would do the same actions
1269         my @drops;
1270         my $hits;
1271         foreach my $hit (reverse @{$res} ) {
1272                 my $macaddress= lc @{$hit}[8];
1273                 my $headertag= @{$hit}[5];
1274                 if(defined($hits->{$macaddress}->{$headertag})) {
1275                         push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1276                 }
1277                 $hits->{$macaddress}->{$headertag}= $hit;
1278         }
1280         # Delete new jobs with a matching job in state 'processing'
1281         foreach my $macaddress (keys %{$hits}) {
1282                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1283                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1284                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1285                         my $res = $job_db->exec_statement( $sql_statement );
1286                         foreach my $hit (@{$res}) {
1287                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1288                         }
1289                 }
1290         }
1292         # Commit deletion
1293         $job_db->exec_statementlist(\@drops);
1295         # Look for new jobs that could be executed
1296         foreach my $macaddress (keys %{$hits}) {
1298                 # Look if there is an executing job
1299                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1300                 my $res = $job_db->exec_statement( $sql_statement );
1302                 # Skip new jobs for host if there is a processing job
1303                 if(defined($res) and defined @{$res}[0]) {
1304                         next;
1305                 }
1307                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1308                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1309                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1311                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1312                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1313                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1315                         # expect macaddress is unique!!!!!!
1316                         my $target = $res_hash->{1}->{hostname};
1318                         # change header
1319                         $job_msg =~ s/<header>job_/<header>gosa_/;
1321                         # add sqlite_id
1322                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1324                         $job_msg =~ /<header>(\S+)<\/header>/;
1325                         my $header = $1 ;
1326                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1328                         # update status in job queue to 'processing'
1329                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1330                         my $res = $job_db->update_dbentry($sql_statement);
1332                         # We don't want parallel processing
1333                         last;
1334                 }
1335         }
1337         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1341 sub watch_for_new_messages {
1342     my ($kernel,$heap) = @_[KERNEL, HEAP];
1343     my @coll_user_msg;   # collection list of outgoing messages
1344     
1345     # check messaging_db for new incoming messages with executable timestamp
1346     my $timestamp = &get_time();
1347     #my $sql_statement = "SELECT * FROM $messaging_tn WHERE (CAST (delivery_time AS INTEGER) + 120) < $timestamp";
1348     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1349     my $res = $messaging_db->exec_statement( $sql_statement );
1351         foreach my $hit (@{$res}) {
1353         # create outgoing messages
1354         my $message_to = @{$hit}[2];
1356         # translate message_to to plain login name
1357         my @reciever_l = ($message_to);  
1358         my $message_id = @{$hit}[8];
1360         #add each outgoing msg to messaging_db
1361         my $reciever;
1362         foreach $reciever (@reciever_l) {
1363             my $sql_statement = "INSERT INTO $messaging_tn (subject, message_from, message_to, flag, direction, delivery_time, message, timestamp, id) ".
1364                 "VALUES ('".
1365                 @{$hit}[0]."', '".   # subject
1366                 @{$hit}[1]."', '".   # message_from
1367                 $reciever."', '".    # message_to
1368                 "none"."', '".       # flag
1369                 "out"."', '".        # direction
1370                 @{$hit}[5]."', '".   # delivery_time
1371                 @{$hit}[6]."', '".   # message
1372                 $timestamp."', '".   # timestamp
1373                 @{$hit}[8].          # id
1374                 "')";
1375             &daemon_log("M DEBUG: $sql_statement", 1);
1376             my $res = $messaging_db->exec_statement($sql_statement);
1377             &daemon_log("M INFO: message '".@{$hit}[8]."' is prepared for delivery to reciever '$reciever'", 5);
1378         }
1380         # send outgoing messages
1381         my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1382         my $res = $messaging_db->exec_statement( $sql_statement );
1383         foreach my $hit (@{$res}) {
1384             # add subject, from, to and message to list coll_user_msg
1385             my @user_msg = [@{$hit}[0], @{$hit}[1], $reciever, @{$hit}[6]];
1386             push( @coll_user_msg, \@user_msg);
1387         }
1389         # send outgoing list to myself (gosa-si-server) to deliver each message to user
1390         # reason for this workaround: if to much messages have to be delivered, it can come to 
1391         # denial of service problems of the server. so, the incoming message list can be processed
1392         # by a forked child and gosa-si-server is always ready to work. 
1393         my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1394         # add to hash 'msg1' => [subject, from, to, message]
1395         # hash to string
1396         # send msg to myself
1397 # TODO
1399         # set incoming message to flag d=deliverd
1400         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1401         &daemon_log("M DEBUG: $sql_statement", 7);
1402         $res = $messaging_db->update_dbentry($sql_statement);
1403         &daemon_log("M INFO: message '".@{$hit}[8]."' is set to flag 'p' (processed)", 5);
1405     }
1406     
1407     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1410     return;
1414 sub watch_for_done_messages {
1415     my ($kernel,$heap) = @_[KERNEL, HEAP];
1417     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1418     return;
1422 sub get_ldap_handle {
1423         my ($session_id) = @_;
1424         my $heap;
1425         my $ldap_handle;
1427         if (not defined $session_id ) { $session_id = 0 };
1429         if ($session_id == 0) {
1430                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1431                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1432                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1434         } else {
1435                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1436                 if( defined $session_reference ) {
1437                         $heap = $session_reference->get_heap();
1438                 }
1440                 if (not defined $heap) {
1441                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1442                         return;
1443                 }
1445                 # TODO: This "if" is nonsense, because it doesn't prove that the
1446                 #       used handle is still valid - or if we've to reconnect...
1447                 #if (not exists $heap->{ldap_handle}) {
1448                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1449                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1450                         $heap->{ldap_handle} = $ldap_handle;
1451                 #}
1452         }
1453         return $ldap_handle;
1457 sub change_fai_state {
1458     my ($st, $targets, $session_id) = @_;
1459     $session_id = 0 if not defined $session_id;
1460     # Set FAI state to localboot
1461     my %mapActions= (
1462         reboot    => '',
1463         update    => 'softupdate',
1464         localboot => 'localboot',
1465         reinstall => 'install',
1466         rescan    => '',
1467         wake      => '',
1468         memcheck  => 'memcheck',
1469         sysinfo   => 'sysinfo',
1470         install   => 'install',
1471     );
1473     # Return if this is unknown
1474     if (!exists $mapActions{ $st }){
1475         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1476       return;
1477     }
1479     my $state= $mapActions{ $st };
1481     my $ldap_handle = &get_ldap_handle($session_id);
1482     if( defined($ldap_handle) ) {
1484       # Build search filter for hosts
1485         my $search= "(&(objectClass=GOhard)";
1486         foreach (@{$targets}){
1487             $search.= "(macAddress=$_)";
1488         }
1489         $search.= ")";
1491       # If there's any host inside of the search string, procress them
1492         if (!($search =~ /macAddress/)){
1493             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1494             return;
1495         }
1497       # Perform search for Unit Tag
1498       my $mesg = $ldap_handle->search(
1499           base   => $ldap_base,
1500           scope  => 'sub',
1501           attrs  => ['dn', 'FAIstate', 'objectClass'],
1502           filter => "$search"
1503           );
1505           if ($mesg->count) {
1506                   my @entries = $mesg->entries;
1507                   foreach my $entry (@entries) {
1508                           # Only modify entry if it is not set to '$state'
1509                           if ($entry->get_value("FAIstate") ne "$state"){
1510                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1511                                   my $result;
1512                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1513                                   if (exists $tmp{'FAIobject'}){
1514                                           if ($state eq ''){
1515                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1516                                                           delete => [ FAIstate => [] ] ]);
1517                                           } else {
1518                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1519                                                           replace => [ FAIstate => $state ] ]);
1520                                           }
1521                                   } elsif ($state ne ''){
1522                                           $result= $ldap_handle->modify($entry->dn, changes => [
1523                                                   add     => [ objectClass => 'FAIobject' ],
1524                                                   add     => [ FAIstate => $state ] ]);
1525                                   }
1527                                   # Errors?
1528                                   if ($result->code){
1529                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1530                                   }
1531                           } else {
1532                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1533                           }  
1534                   }
1535           }
1536     # if no ldap handle defined
1537     } else {
1538         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1539     }
1544 sub change_goto_state {
1545     my ($st, $targets, $session_id) = @_;
1546     $session_id = 0  if not defined $session_id;
1548     # Switch on or off?
1549     my $state= $st eq 'active' ? 'active': 'locked';
1551     my $ldap_handle = &get_ldap_handle($session_id);
1552     if( defined($ldap_handle) ) {
1554       # Build search filter for hosts
1555       my $search= "(&(objectClass=GOhard)";
1556       foreach (@{$targets}){
1557         $search.= "(macAddress=$_)";
1558       }
1559       $search.= ")";
1561       # If there's any host inside of the search string, procress them
1562       if (!($search =~ /macAddress/)){
1563         return;
1564       }
1566       # Perform search for Unit Tag
1567       my $mesg = $ldap_handle->search(
1568           base   => $ldap_base,
1569           scope  => 'sub',
1570           attrs  => ['dn', 'gotoMode'],
1571           filter => "$search"
1572           );
1574       if ($mesg->count) {
1575         my @entries = $mesg->entries;
1576         foreach my $entry (@entries) {
1578           # Only modify entry if it is not set to '$state'
1579           if ($entry->get_value("gotoMode") ne $state){
1581             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1582             my $result;
1583             $result= $ldap_handle->modify($entry->dn, changes => [
1584                                                 replace => [ gotoMode => $state ] ]);
1586             # Errors?
1587             if ($result->code){
1588               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1589             }
1591           }
1592         }
1593       }
1595     }
1599 sub create_fai_server_db {
1600     my ($table_name, $kernel) = @_;
1601         my $result;
1602     my $ldap_handle = &get_ldap_handle();
1603         if(defined($ldap_handle)) {
1604                 daemon_log("INFO: create_fai_server_db: start", 5);
1605                 my $mesg= $ldap_handle->search(
1606                         base   => $ldap_base,
1607                         scope  => 'sub',
1608                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1609                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1610                 );
1611                 if($mesg->{'resultCode'} == 0 &&
1612                    $mesg->count != 0) {
1613                    foreach my $entry (@{$mesg->{entries}}) {
1614                            if($entry->exists('FAIrepository')) {
1615                                    # Add an entry for each Repository configured for server
1616                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1617                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1618                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1619                                                    $result= $fai_server_db->add_dbentry( { 
1620                                                                    table => $table_name,
1621                                                                    primkey => ['server', 'release', 'tag'],
1622                                                                    server => $tmp_url,
1623                                                                    release => $tmp_release,
1624                                                                    sections => $tmp_sections,
1625                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1626                                                            } );
1627                                            }
1628                                    }
1629                            }
1630                    }
1631                 daemon_log("INFO: create_fai_server_db: finished", 5);
1633                 # TODO: Find a way to post the 'create_packages_list_db' event
1634                 &create_packages_list_db($ldap_handle);
1635         }       
1636     
1637     $ldap_handle->disconnect;
1638         return $result;
1641 sub run_create_fai_server_db {
1642     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1643     my $task = POE::Wheel::Run->new(
1644             Program => sub { &create_fai_server_db($table_name,$kernel) },
1645             StdoutEvent  => "session_run_result",
1646             StderrEvent  => "session_run_debug",
1647             CloseEvent   => "session_run_done",
1648             );
1650     $heap->{task}->{ $task->ID } = $task;
1651     return;
1655 sub create_fai_release_db {
1656         my ($table_name) = @_;
1657         my $result;
1659     my $ldap_handle = &get_ldap_handle();
1660         if(defined($ldap_handle)) {
1661                 daemon_log("INFO: create_fai_release_db: start",5);
1662                 my $mesg= $ldap_handle->search(
1663                         base   => $ldap_base,
1664                         scope  => 'sub',
1665                         attrs  => [],
1666                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1667                 );
1668                 if($mesg->{'resultCode'} == 0 &&
1669                         $mesg->count != 0) {
1670                         # Walk through all possible FAI container ou's
1671                         my @sql_list;
1672                         my $timestamp= &get_time();
1673                         foreach my $ou (@{$mesg->{entries}}) {
1674                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1675                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1676                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1677                                         if(@tmp_array) {
1678                                                 foreach my $entry (@tmp_array) {
1679                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1680                                                                 my $sql= 
1681                                                                 "INSERT INTO $table_name "
1682                                                                 ."(timestamp, release, class, type, state) VALUES ("
1683                                                                 .$timestamp.","
1684                                                                 ."'".$entry->{'release'}."',"
1685                                                                 ."'".$entry->{'class'}."',"
1686                                                                 ."'".$entry->{'type'}."',"
1687                                                                 ."'".$entry->{'state'}."')";
1688                                                                 push @sql_list, $sql;
1689                                                         }
1690                                                 }
1691                                         }
1692                                 }
1693                         }
1694                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1695                         if(@sql_list) {
1696                                 unshift @sql_list, "DELETE FROM $table_name";
1697                                 $fai_server_db->exec_statementlist(\@sql_list);
1698                         }
1699                         daemon_log("DEBUG: Done with inserting",6);
1700                 }
1701                 daemon_log("INFO: create_fai_release_db: finished",5);
1702         }
1703     $ldap_handle->disconnect;
1704         return $result;
1706 sub run_create_fai_release_db {
1707     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1708     my $task = POE::Wheel::Run->new(
1709             Program => sub { &create_fai_release_db($table_name) },
1710             StdoutEvent  => "session_run_result",
1711             StderrEvent  => "session_run_debug",
1712             CloseEvent   => "session_run_done",
1713             );
1715     $heap->{task}->{ $task->ID } = $task;
1716     return;
1719 sub get_fai_types {
1720         my $tmp_classes = shift || return undef;
1721         my @result;
1723         foreach my $type(keys %{$tmp_classes}) {
1724                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1725                         my $entry = {
1726                                 type => $type,
1727                                 state => $tmp_classes->{$type}[0],
1728                         };
1729                         push @result, $entry;
1730                 }
1731         }
1733         return @result;
1736 sub get_fai_state {
1737         my $result = "";
1738         my $tmp_classes = shift || return $result;
1740         foreach my $type(keys %{$tmp_classes}) {
1741                 if(defined($tmp_classes->{$type}[0])) {
1742                         $result = $tmp_classes->{$type}[0];
1743                         
1744                 # State is equal for all types in class
1745                         last;
1746                 }
1747         }
1749         return $result;
1752 sub resolve_fai_classes {
1753         my ($fai_base, $ldap_handle) = @_;
1754         my $result;
1755         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1756         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1757         my $fai_classes;
1759         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1760         my $mesg= $ldap_handle->search(
1761                 base   => $fai_base,
1762                 scope  => 'sub',
1763                 attrs  => ['cn','objectClass','FAIstate'],
1764                 filter => $fai_filter,
1765         );
1766         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1768         if($mesg->{'resultCode'} == 0 &&
1769                 $mesg->count != 0) {
1770                 foreach my $entry (@{$mesg->{entries}}) {
1771                         if($entry->exists('cn')) {
1772                                 my $tmp_dn= $entry->dn();
1774                                 # Skip classname and ou dn parts for class
1775                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1777                                 # Skip classes without releases
1778                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1779                                         next;
1780                                 }
1782                                 my $tmp_cn= $entry->get_value('cn');
1783                                 my $tmp_state= $entry->get_value('FAIstate');
1785                                 my $tmp_type;
1786                                 # Get FAI type
1787                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1788                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1789                                                 $tmp_type= $oclass;
1790                                                 last;
1791                                         }
1792                                 }
1794                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1795                                         # A Subrelease
1796                                         my @sub_releases = split(/,/, $tmp_release);
1798                                         # Walk through subreleases and build hash tree
1799                                         my $hash;
1800                                         while(my $tmp_sub_release = pop @sub_releases) {
1801                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1802                                         }
1803                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1804                                 } else {
1805                                         # A branch, no subrelease
1806                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1807                                 }
1808                         } elsif (!$entry->exists('cn')) {
1809                                 my $tmp_dn= $entry->dn();
1810                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1812                                 # Skip classes without releases
1813                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1814                                         next;
1815                                 }
1817                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1818                                         # A Subrelease
1819                                         my @sub_releases= split(/,/, $tmp_release);
1821                                         # Walk through subreleases and build hash tree
1822                                         my $hash;
1823                                         while(my $tmp_sub_release = pop @sub_releases) {
1824                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1825                                         }
1826                                         # Remove the last two characters
1827                                         chop($hash);
1828                                         chop($hash);
1830                                         eval('$fai_classes->'.$hash.'= {}');
1831                                 } else {
1832                                         # A branch, no subrelease
1833                                         if(!exists($fai_classes->{$tmp_release})) {
1834                                                 $fai_classes->{$tmp_release} = {};
1835                                         }
1836                                 }
1837                         }
1838                 }
1840                 # The hash is complete, now we can honor the copy-on-write based missing entries
1841                 foreach my $release (keys %$fai_classes) {
1842                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1843                 }
1844         }
1845         return $result;
1848 sub apply_fai_inheritance {
1849        my $fai_classes = shift || return {};
1850        my $tmp_classes;
1852        # Get the classes from the branch
1853        foreach my $class (keys %{$fai_classes}) {
1854                # Skip subreleases
1855                if($class =~ /^ou=.*$/) {
1856                        next;
1857                } else {
1858                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1859                }
1860        }
1862        # Apply to each subrelease
1863        foreach my $subrelease (keys %{$fai_classes}) {
1864                if($subrelease =~ /ou=/) {
1865                        foreach my $tmp_class (keys %{$tmp_classes}) {
1866                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1867                                        $fai_classes->{$subrelease}->{$tmp_class} =
1868                                        deep_copy($tmp_classes->{$tmp_class});
1869                                } else {
1870                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1871                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1872                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1873                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1874                                                }
1875                                        }
1876                                }
1877                        }
1878                }
1879        }
1881        # Find subreleases in deeper levels
1882        foreach my $subrelease (keys %{$fai_classes}) {
1883                if($subrelease =~ /ou=/) {
1884                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1885                                if($subsubrelease =~ /ou=/) {
1886                                        apply_fai_inheritance($fai_classes->{$subrelease});
1887                                }
1888                        }
1889                }
1890        }
1892        return $fai_classes;
1895 sub get_fai_release_entries {
1896         my $tmp_classes = shift || return;
1897         my $parent = shift || "";
1898         my @result = shift || ();
1900         foreach my $entry (keys %{$tmp_classes}) {
1901                 if(defined($entry)) {
1902                         if($entry =~ /^ou=.*$/) {
1903                                 my $release_name = $entry;
1904                                 $release_name =~ s/ou=//g;
1905                                 if(length($parent)>0) {
1906                                         $release_name = $parent."/".$release_name;
1907                                 }
1908                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1909                                 foreach my $bufentry(@bufentries) {
1910                                         push @result, $bufentry;
1911                                 }
1912                         } else {
1913                                 my @types = get_fai_types($tmp_classes->{$entry});
1914                                 foreach my $type (@types) {
1915                                         push @result, 
1916                                         {
1917                                                 'class' => $entry,
1918                                                 'type' => $type->{'type'},
1919                                                 'release' => $parent,
1920                                                 'state' => $type->{'state'},
1921                                         };
1922                                 }
1923                         }
1924                 }
1925         }
1927         return @result;
1930 sub deep_copy {
1931         my $this = shift;
1932         if (not ref $this) {
1933                 $this;
1934         } elsif (ref $this eq "ARRAY") {
1935                 [map deep_copy($_), @$this];
1936         } elsif (ref $this eq "HASH") {
1937                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1938         } else { die "what type is $_?" }
1942 sub session_run_result {
1943     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1944     $kernel->sig(CHLD => "child_reap");
1947 sub session_run_debug {
1948     my $result = $_[ARG0];
1949     print STDERR "$result\n";
1952 sub session_run_done {
1953     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1954     delete $heap->{task}->{$task_id};
1957 sub create_sources_list {
1958     my ($ldap_handle) = @_;
1959         my $result="/tmp/gosa_si_tmp_sources_list";
1961         # Remove old file
1962         if(stat($result)) {
1963                 unlink($result);
1964         }
1966         my $fh;
1967         open($fh, ">$result") or return undef;
1968         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1969                 my $mesg=$ldap_handle->search(
1970                                 base    => $ldap_server_dn,
1971                                 scope   => 'base',
1972                                 attrs   => 'FAIrepository',
1973                                 filter  => 'objectClass=FAIrepositoryServer'
1974                                 );
1975                 if($mesg->count) {
1976                         foreach my $entry(@{$mesg->{'entries'}}) {
1977                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
1978                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
1979                                         my $line = "deb $server $release";
1980                                         $sections =~ s/,/ /g;
1981                                         $line.= " $sections";
1982                                         print $fh $line."\n";
1983                                 }
1984                         }
1985                 }
1986         }
1987         close($fh);
1989         return $result;
1992 sub create_packages_list_db {
1993     my ($ldap_handle, $sources_file) = @_ ;
1995         if (not defined $ldap_handle) { 
1996                 $ldap_handle= &get_ldap_handle();
1998                 if (not defined $ldap_handle) {
1999                         daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
2000                         return;
2001                 }
2002         }
2004     if (not defined $sources_file) { 
2005         $sources_file = &create_sources_list($ldap_handle);
2006     }
2008     my $line;
2009     daemon_log("INFO: create_packages_list_db: start", 5); 
2011     open(CONFIG, "<$sources_file") or do {
2012         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2013         return;
2014     };
2015     
2016     # Read lines
2017     while ($line = <CONFIG>){
2018         # Unify
2019         chop($line);
2020         $line =~ s/^\s+//;
2021         $line =~ s/^\s+/ /;
2023         # Strip comments
2024         $line =~ s/#.*$//g;
2026         # Skip empty lines
2027         if ($line =~ /^\s*$/){
2028             next;
2029         }
2031         # Interpret deb line
2032         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2033             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2034             my $section;
2035             foreach $section (split(' ', $sections)){
2036                 &parse_package_info( $baseurl, $dist, $section );
2037             }
2038         }
2039     }
2041     close (CONFIG);
2043     daemon_log("INFO: create_packages_list_db: finished", 5); 
2044     return;
2047 sub run_create_packages_list_db {
2048     my ($session, $heap) = @_[SESSION, HEAP];
2049     my $task = POE::Wheel::Run->new(
2050             Program => sub {&create_packages_list_db},
2051             StdoutEvent  => "session_run_result",
2052             StderrEvent  => "session_run_debug",
2053             CloseEvent   => "session_run_done",
2054             );
2055     $heap->{task}->{ $task->ID } = $task;
2058 sub parse_package_info {
2059   my ($baseurl, $dist, $section)= @_;
2060   my ($package);
2062   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2063   $repo_dirs{ "${repo_path}/pool" } = 1;
2065   foreach $package ("Packages.gz"){
2066     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2067     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
2068     parse_package( "$outdir/$dist/$section", $dist, $path );
2069   }
2070   find(\&cleanup_and_extract, keys( %repo_dirs ) );
2073 sub get_package {
2074   my ($url, $dest)= @_;
2076   my $tpath = dirname($dest);
2077   -d "$tpath" || mkpath "$tpath";
2079   # This is ugly, but I've no time to take a look at "how it works in perl"
2080   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2081       system("gunzip -cd '$dest' > '$dest.in'");
2082       unlink($dest);
2083   } else {
2084       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2085   }
2086   return 0;
2089 sub parse_package {
2090     my ($path, $dist, $srv_path)= @_;
2091     my ($package, $version, $section, $description);
2092     my @sql_list;
2093     my $PACKAGES;
2095     if(not stat("$path.in")) {
2096         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2097         return;
2098     }
2100     open($PACKAGES, "<$path.in");
2101         if(not defined($PACKAGES)) {
2102         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
2103         return;
2104     }
2106     # Read lines
2107     while (<$PACKAGES>){
2108         my $line = $_;
2109         # Unify
2110         chop($line);
2112         # Use empty lines as a trigger
2113         if ($line =~ /^\s*$/){
2114             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2115             push(@sql_list, $sql);
2116             $package = "none";
2117             $version = "none";
2118             $section = "none";
2119             $description = "none"; 
2120             next;
2121         }
2123         # Trigger for package name
2124         if ($line =~ /^Package:\s/){
2125             ($package)= ($line =~ /^Package: (.*)$/);
2126             next;
2127         }
2129         # Trigger for version
2130         if ($line =~ /^Version:\s/){
2131             ($version)= ($line =~ /^Version: (.*)$/);
2132             next;
2133         }
2135         # Trigger for description
2136         if ($line =~ /^Description:\s/){
2137             ($description)= ($line =~ /^Description: (.*)$/);
2138             next;
2139         }
2141         # Trigger for section
2142         if ($line =~ /^Section:\s/){
2143             ($section)= ($line =~ /^Section: (.*)$/);
2144             next;
2145         }
2147         # Trigger for filename
2148         if ($line =~ /^Filename:\s/){
2149                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2150                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2151                 next;
2152         }
2153     }
2155     close( $PACKAGES );
2156     unlink( "$path.in" );
2157     
2158     $packages_list_db->exec_statementlist(\@sql_list);
2161 sub store_fileinfo {
2162   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2164   my %fileinfo = (
2165     'package' => $package,
2166     'dist' => $dist,
2167     'version' => $vers,
2168   );
2170   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2173 sub cleanup_and_extract {
2174   my $fileinfo = $repo_files{ $File::Find::name };
2176   if( defined $fileinfo ) {
2178     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2179     my $sql;
2180     my $package = $fileinfo->{ 'package' };
2181     my $newver = $fileinfo->{ 'version' };
2183     mkpath($dir);
2184     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2186     if( -f "$dir/DEBIAN/templates" ) {
2188       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2190       my $tmpl= "";
2191       {
2192           local $/=undef;
2193           open FILE, "$dir/DEBIAN/templates";
2194           $tmpl = &encode_base64(<FILE>);
2195           close FILE;
2196       }
2197       rmtree("$dir/DEBIAN/templates");
2199       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2201     } else {
2202       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2203     }
2205     my $res= $main::packages_list_db->update_dbentry($sql);
2206   }
2210 #==== MAIN = main ==============================================================
2211 #  parse commandline options
2212 Getopt::Long::Configure( "bundling" );
2213 GetOptions("h|help" => \&usage,
2214         "c|config=s" => \$cfg_file,
2215         "f|foreground" => \$foreground,
2216         "v|verbose+" => \$verbose,
2217         "no-bus+" => \$no_bus,
2218         "no-arp+" => \$no_arp,
2219            );
2221 #  read and set config parameters
2222 &check_cmdline_param ;
2223 &read_configfile;
2224 &check_pid;
2226 $SIG{CHLD} = 'IGNORE';
2228 # forward error messages to logfile
2229 if( ! $foreground ) {
2230   open( STDIN,  '+>/dev/null' );
2231   open( STDOUT, '+>&STDIN'    );
2232   open( STDERR, '+>&STDIN'    );
2235 # Just fork, if we are not in foreground mode
2236 if( ! $foreground ) { 
2237     chdir '/'                 or die "Can't chdir to /: $!";
2238     $pid = fork;
2239     setsid                    or die "Can't start a new session: $!";
2240     umask 0;
2241 } else { 
2242     $pid = $$; 
2245 # Do something useful - put our PID into the pid_file
2246 if( 0 != $pid ) {
2247     open( LOCK_FILE, ">$pid_file" );
2248     print LOCK_FILE "$pid\n";
2249     close( LOCK_FILE );
2250     if( !$foreground ) { 
2251         exit( 0 ) 
2252     };
2255 daemon_log(" ", 1);
2256 daemon_log("$0 started!", 1);
2258 if ($no_bus > 0) {
2259     $bus_activ = "false"
2262 # connect to gosa-si job queue
2263 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2264 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2266 # connect to known_clients_db
2267 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2268 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2270 # connect to known_server_db
2271 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2272 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2274 # connect to login_usr_db
2275 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2276 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2278 # connect to fai_server_db and fai_release_db
2279 unlink($fai_server_file_name);
2280 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2281 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2282 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2284 # connect to packages_list_db
2285 unlink($packages_list_file_name);
2286 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2287 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2289 # connect to messaging_db
2290 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2291 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2294 # create xml object used for en/decrypting
2295 $xml = new XML::Simple();
2297 # create socket for incoming xml messages
2299 POE::Component::Server::TCP->new(
2300         Port => $server_port,
2301         ClientInput => sub {
2302         my ($kernel, $input) = @_[KERNEL, ARG0];
2303         push(@tasks, $input);
2304         $kernel->yield("next_task");
2305         },
2306     InlineStates => {
2307         next_task => \&next_task,
2308         task_result => \&handle_task_result,
2309         task_done   => \&handle_task_done,
2310         task_debug  => \&handle_task_debug,
2311         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2312     }
2313 );
2315 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2317 # create session for repeatedly checking the job queue for jobs
2318 POE::Session->create(
2319         inline_states => {
2320                 _start => \&_start,
2321                 sig_handler => \&sig_handler,
2322         watch_for_new_messages => \&watch_for_new_messages,
2323         watch_for_done_messages => \&watch_for_done_messages,
2324                 watch_for_new_jobs => \&watch_for_new_jobs,
2325         watch_for_done_jobs => \&watch_for_done_jobs,
2326         create_packages_list_db => \&run_create_packages_list_db,
2327         create_fai_server_db => \&run_create_fai_server_db,
2328         create_fai_release_db => \&run_create_fai_release_db,
2329         session_run_result => \&session_run_result,
2330         session_run_debug => \&session_run_debug,
2331         session_run_done => \&session_run_done,
2332         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2333         }
2334 );
2337 # import all modules
2338 &import_modules;
2340 # check wether all modules are gosa-si valid passwd check
2342 POE::Kernel->run();
2343 exit;