Code

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