Code

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