Code

f33f90a42cbc7a2d768905ae979854b3fd7c0d26
[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 = ("id INTEGER", "subject", "message_from", "message_to", 
171         "flag", "direction", "delivery_time", "message", "timestamp" );
172 my $messaging_file_name;
174 # path to directory to store client install log files
175 our $client_fai_log_dir = "/var/log/fai"; 
177 # queue which stores taskes until one of the $max_children children are ready to process the task
178 my @tasks = qw();
179 my $max_children = 2;
182 %cfg_defaults = (
183 "general" => {
184     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
185     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
186     },
187 "bus" => {
188     "activ" => [\$bus_activ, "true"],
189     },
190 "server" => {
191     "port" => [\$server_port, "20081"],
192     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
193     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
194     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
195     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
196     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
197     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
198     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
199     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
200     "repo-path" => [\$repo_path, '/srv/www/repository'],
201     "ldap-uri" => [\$ldap_uri, ""],
202     "ldap-base" => [\$ldap_base, ""],
203     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
204     "ldap-admin-password" => [\$ldap_admin_password, ""],
205     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
206     "max-clients" => [\$max_clients, 10],
207     },
208 "GOsaPackages" => {
209     "ip" => [\$gosa_ip, "0.0.0.0"],
210     "port" => [\$gosa_port, "20082"],
211     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
212     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
213     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
214     "key" => [\$GosaPackages_key, "none"],
215     },
216 "SIPackages" => {
217     "key" => [\$SIPackages_key, "none"],
218     },
219 );
222 #===  FUNCTION  ================================================================
223 #         NAME:  usage
224 #   PARAMETERS:  nothing
225 #      RETURNS:  nothing
226 #  DESCRIPTION:  print out usage text to STDERR
227 #===============================================================================
228 sub usage {
229     print STDERR << "EOF" ;
230 usage: $prg [-hvf] [-c config]
232            -h        : this (help) message
233            -c <file> : config file
234            -f        : foreground, process will not be forked to background
235            -v        : be verbose (multiple to increase verbosity)
236            -no-bus   : starts $prg without connection to bus
237            -no-arp   : starts $prg without connection to arp module
238  
239 EOF
240     print "\n" ;
244 #===  FUNCTION  ================================================================
245 #         NAME:  read_configfile
246 #   PARAMETERS:  cfg_file - string -
247 #      RETURNS:  nothing
248 #  DESCRIPTION:  read cfg_file and set variables
249 #===============================================================================
250 sub read_configfile {
251     my $cfg;
252     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
253         if( -r $cfg_file ) {
254             $cfg = Config::IniFiles->new( -file => $cfg_file );
255         } else {
256             print STDERR "Couldn't read config file!\n";
257         }
258     } else {
259         $cfg = Config::IniFiles->new() ;
260     }
261     foreach my $section (keys %cfg_defaults) {
262         foreach my $param (keys %{$cfg_defaults{ $section }}) {
263             my $pinfo = $cfg_defaults{ $section }{ $param };
264             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
265         }
266     }
270 #===  FUNCTION  ================================================================
271 #         NAME:  logging
272 #   PARAMETERS:  level - string - default 'info'
273 #                msg - string -
274 #                facility - string - default 'LOG_DAEMON'
275 #      RETURNS:  nothing
276 #  DESCRIPTION:  function for logging
277 #===============================================================================
278 sub daemon_log {
279     # log into log_file
280     my( $msg, $level ) = @_;
281     if(not defined $msg) { return }
282     if(not defined $level) { $level = 1 }
283     if(defined $log_file){
284         open(LOG_HANDLE, ">>$log_file");
285         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
286             print STDERR "cannot open $log_file: $!";
287             return }
288             chomp($msg);
289                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
290             if($level <= $verbose){
291                 my ($seconds, $minutes, $hours, $monthday, $month,
292                         $year, $weekday, $yearday, $sommertime) = localtime(time);
293                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
294                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
295                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
296                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
297                 $month = $monthnames[$month];
298                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
299                 $year+=1900;
300                 my $name = $prg;
302                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
303                 print LOG_HANDLE $log_msg;
304                 if( $foreground ) { 
305                     print STDERR $log_msg;
306                 }
307             }
308         close( LOG_HANDLE );
309     }
313 #===  FUNCTION  ================================================================
314 #         NAME:  check_cmdline_param
315 #   PARAMETERS:  nothing
316 #      RETURNS:  nothing
317 #  DESCRIPTION:  validates commandline parameter
318 #===============================================================================
319 sub check_cmdline_param () {
320     my $err_config;
321     my $err_counter = 0;
322         if(not defined($cfg_file)) {
323                 $cfg_file = "/etc/gosa-si/server.conf";
324                 if(! -r $cfg_file) {
325                         $err_config = "please specify a config file";
326                         $err_counter += 1;
327                 }
328     }
329     if( $err_counter > 0 ) {
330         &usage( "", 1 );
331         if( defined( $err_config)) { print STDERR "$err_config\n"}
332         print STDERR "\n";
333         exit( -1 );
334     }
338 #===  FUNCTION  ================================================================
339 #         NAME:  check_pid
340 #   PARAMETERS:  nothing
341 #      RETURNS:  nothing
342 #  DESCRIPTION:  handels pid processing
343 #===============================================================================
344 sub check_pid {
345     $pid = -1;
346     # Check, if we are already running
347     if( open(LOCK_FILE, "<$pid_file") ) {
348         $pid = <LOCK_FILE>;
349         if( defined $pid ) {
350             chomp( $pid );
351             if( -f "/proc/$pid/stat" ) {
352                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
353                 if( $stat ) {
354                                         daemon_log("ERROR: Already running",1);
355                     close( LOCK_FILE );
356                     exit -1;
357                 }
358             }
359         }
360         close( LOCK_FILE );
361         unlink( $pid_file );
362     }
364     # create a syslog msg if it is not to possible to open PID file
365     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
366         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
367         if (open(LOCK_FILE, '<', $pid_file)
368                 && ($pid = <LOCK_FILE>))
369         {
370             chomp($pid);
371             $msg .= "(PID $pid)\n";
372         } else {
373             $msg .= "(unable to read PID)\n";
374         }
375         if( ! ($foreground) ) {
376             openlog( $0, "cons,pid", "daemon" );
377             syslog( "warning", $msg );
378             closelog();
379         }
380         else {
381             print( STDERR " $msg " );
382         }
383         exit( -1 );
384     }
387 #===  FUNCTION  ================================================================
388 #         NAME:  import_modules
389 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
390 #                are stored
391 #      RETURNS:  nothing
392 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
393 #                state is on is imported by "require 'file';"
394 #===============================================================================
395 sub import_modules {
396     daemon_log(" ", 1);
398     if (not -e $modules_path) {
399         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
400     }
402     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
403     while (defined (my $file = readdir (DIR))) {
404         if (not $file =~ /(\S*?).pm$/) {
405             next;
406         }
407                 my $mod_name = $1;
409         if( $file =~ /ArpHandler.pm/ ) {
410             if( $no_arp > 0 ) {
411                 next;
412             }
413         }
414         
415         eval { require $file; };
416         if ($@) {
417             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
418             daemon_log("$@", 5);
419                 } else {
420                         my $info = eval($mod_name.'::get_module_info()');
421                         # Only load module if get_module_info() returns a non-null object
422                         if( $info ) {
423                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
424                                 $known_modules->{$mod_name} = $info;
425                                 daemon_log("INFO: module $mod_name loaded", 5);
426                         }
427                 }
428     }   
429     close (DIR);
433 #===  FUNCTION  ================================================================
434 #         NAME:  sig_int_handler
435 #   PARAMETERS:  signal - string - signal arose from system
436 #      RETURNS:  noting
437 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
438 #===============================================================================
439 sub sig_int_handler {
440     my ($signal) = @_;
442 #       if (defined($ldap_handle)) {
443 #               $ldap_handle->disconnect;
444 #       }
445     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
446     
448     daemon_log("shutting down gosa-si-server", 1);
449     system("kill `ps -C gosa-si-server -o pid=`");
451 $SIG{INT} = \&sig_int_handler;
454 sub check_key_and_xml_validity {
455     my ($crypted_msg, $module_key, $session_id) = @_;
456     my $msg;
457     my $msg_hash;
458     my $error_string;
459     eval{
460         $msg = &decrypt_msg($crypted_msg, $module_key);
462         if ($msg =~ /<xml>/i){
463             $msg =~ s/\s+/ /g;  # just for better daemon_log
464             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
465             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
467             ##############
468             # check header
469             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
470             my $header_l = $msg_hash->{'header'};
471             if( 1 > @{$header_l} ) { die 'empty header tag'; }
472             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
473             my $header = @{$header_l}[0];
474             if( 0 == length $header) { die 'empty string in header tag'; }
476             ##############
477             # check source
478             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
479             my $source_l = $msg_hash->{'source'};
480             if( 1 > @{$source_l} ) { die 'empty source tag'; }
481             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
482             my $source = @{$source_l}[0];
483             if( 0 == length $source) { die 'source error'; }
485             ##############
486             # check target
487             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
488             my $target_l = $msg_hash->{'target'};
489             if( 1 > @{$target_l} ) { die 'empty target tag'; }
490         }
491     };
492     if($@) {
493         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
494         $msg = undef;
495         $msg_hash = undef;
496     }
498     return ($msg, $msg_hash);
502 sub check_outgoing_xml_validity {
503     my ($msg) = @_;
505     my $msg_hash;
506     eval{
507         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
509         ##############
510         # check header
511         my $header_l = $msg_hash->{'header'};
512         if( 1 != @{$header_l} ) {
513             die 'no or more than one headers specified';
514         }
515         my $header = @{$header_l}[0];
516         if( 0 == length $header) {
517             die 'header has length 0';
518         }
520         ##############
521         # check source
522         my $source_l = $msg_hash->{'source'};
523         if( 1 != @{$source_l} ) {
524             die 'no or more than 1 sources specified';
525         }
526         my $source = @{$source_l}[0];
527         if( 0 == length $source) {
528             die 'source has length 0';
529         }
530         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
531                 $source =~ /^GOSA$/i ) {
532             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
533         }
534         
535         ##############
536         # check target  
537         my $target_l = $msg_hash->{'target'};
538         if( 0 == @{$target_l} ) {
539             die "no targets specified";
540         }
541         foreach my $target (@$target_l) {
542             if( 0 == length $target) {
543                 die "target has length 0";
544             }
545             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
546                     $target =~ /^GOSA$/i ||
547                     $target =~ /^\*$/ ||
548                     $target =~ /KNOWN_SERVER/i ||
549                     $target =~ /JOBDB/i ||
550                     $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 ){
551                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
552             }
553         }
554     };
555     if($@) {
556         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
557         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
558         $msg_hash = undef;
559     }
561     return ($msg_hash);
565 sub input_from_known_server {
566     my ($input, $remote_ip, $session_id) = @_ ;  
567     my ($msg, $msg_hash, $module);
569     my $sql_statement= "SELECT * FROM known_server";
570     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
572     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
573         my $host_name = $hit->{hostname};
574         if( not $host_name =~ "^$remote_ip") {
575             next;
576         }
577         my $host_key = $hit->{hostkey};
578         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
579         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
581         # check if module can open msg envelope with module key
582         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
583         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
584             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
585             daemon_log("$@", 8);
586             next;
587         }
588         else {
589             $msg = $tmp_msg;
590             $msg_hash = $tmp_msg_hash;
591             $module = "SIPackages";
592             last;
593         }
594     }
596     if( (!$msg) || (!$msg_hash) || (!$module) ) {
597         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
598     }
599   
600     return ($msg, $msg_hash, $module);
604 sub input_from_known_client {
605     my ($input, $remote_ip, $session_id) = @_ ;  
606     my ($msg, $msg_hash, $module);
608     my $sql_statement= "SELECT * FROM known_clients";
609     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
610     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
611         my $host_name = $hit->{hostname};
612         if( not $host_name =~ /^$remote_ip:\d*$/) {
613                 next;
614                 }
615         my $host_key = $hit->{hostkey};
616         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
617         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
619         # check if module can open msg envelope with module key
620         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
622         if( (!$msg) || (!$msg_hash) ) {
623             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
624             &daemon_log("$@", 8);
625             next;
626         }
627         else {
628             $module = "SIPackages";
629             last;
630         }
631     }
633     if( (!$msg) || (!$msg_hash) || (!$module) ) {
634         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
635     }
637     return ($msg, $msg_hash, $module);
641 sub input_from_unknown_host {
642     no strict "refs";
643     my ($input, $session_id) = @_ ;
644     my ($msg, $msg_hash, $module);
645     my $error_string;
646     
647         my %act_modules = %$known_modules;
649         while( my ($mod, $info) = each(%act_modules)) {
651         # check a key exists for this module
652         my $module_key = ${$mod."_key"};
653         if( not defined $module_key ) {
654             if( $mod eq 'ArpHandler' ) {
655                 next;
656             }
657             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
658             next;
659         }
660         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
662         # check if module can open msg envelope with module key
663         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
664         if( (not defined $msg) || (not defined $msg_hash) ) {
665             next;
666         }
667         else {
668             $module = $mod;
669             last;
670         }
671     }
673     if( (!$msg) || (!$msg_hash) || (!$module)) {
674         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
675     }
677     return ($msg, $msg_hash, $module);
681 sub create_ciphering {
682     my ($passwd) = @_;
683         if((!defined($passwd)) || length($passwd)==0) {
684                 $passwd = "";
685         }
686     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
687     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
688     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
689     $my_cipher->set_iv($iv);
690     return $my_cipher;
694 sub encrypt_msg {
695     my ($msg, $key) = @_;
696     my $my_cipher = &create_ciphering($key);
697     my $len;
698     {
699             use bytes;
700             $len= 16-length($msg)%16;
701     }
702     $msg = "\0"x($len).$msg;
703     $msg = $my_cipher->encrypt($msg);
704     chomp($msg = &encode_base64($msg));
705     # there are no newlines allowed inside msg
706     $msg=~ s/\n//g;
707     return $msg;
711 sub decrypt_msg {
713     my ($msg, $key) = @_ ;
714     $msg = &decode_base64($msg);
715     my $my_cipher = &create_ciphering($key);
716     $msg = $my_cipher->decrypt($msg); 
717     $msg =~ s/\0*//g;
718     return $msg;
722 sub get_encrypt_key {
723     my ($target) = @_ ;
724     my $encrypt_key;
725     my $error = 0;
727     # target can be in known_server
728     if( not defined $encrypt_key ) {
729         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
730         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
731         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
732             my $host_name = $hit->{hostname};
733             if( $host_name ne $target ) {
734                 next;
735             }
736             $encrypt_key = $hit->{hostkey};
737             last;
738         }
739     }
741     # target can be in known_client
742     if( not defined $encrypt_key ) {
743         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
744         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
745         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
746             my $host_name = $hit->{hostname};
747             if( $host_name ne $target ) {
748                 next;
749             }
750             $encrypt_key = $hit->{hostkey};
751             last;
752         }
753     }
755     return $encrypt_key;
759 #===  FUNCTION  ================================================================
760 #         NAME:  open_socket
761 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
762 #                [PeerPort] string necessary if port not appended by PeerAddr
763 #      RETURNS:  socket IO::Socket::INET
764 #  DESCRIPTION:  open a socket to PeerAddr
765 #===============================================================================
766 sub open_socket {
767     my ($PeerAddr, $PeerPort) = @_ ;
768     if(defined($PeerPort)){
769         $PeerAddr = $PeerAddr.":".$PeerPort;
770     }
771     my $socket;
772     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
773             Porto => "tcp",
774             Type => SOCK_STREAM,
775             Timeout => 5,
776             );
777     if(not defined $socket) {
778         return;
779     }
780 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
781     return $socket;
785 #===  FUNCTION  ================================================================
786 #         NAME:  get_ip 
787 #   PARAMETERS:  interface name (i.e. eth0)
788 #      RETURNS:  (ip address) 
789 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
790 #===============================================================================
791 sub get_ip {
792         my $ifreq= shift;
793         my $result= "";
794         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
795         my $proto= getprotobyname('ip');
797         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
798                 or die "socket: $!";
800         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
801                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
802                 my ($port, $addr) = sockaddr_in $sin;
803                 my $ip            = inet_ntoa $addr;
805                 if ($ip && length($ip) > 0) {
806                         $result = $ip;
807                 }
808         }
810         return $result;
814 sub get_local_ip_for_remote_ip {
815         my $remote_ip= shift;
816         my $result="0.0.0.0";
818         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
819                 if($remote_ip eq "127.0.0.1") {
820                         $result = "127.0.0.1";
821                 } else {
822                         my $PROC_NET_ROUTE= ('/proc/net/route');
824                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
825                                 or die "Could not open $PROC_NET_ROUTE";
827                         my @ifs = <PROC_NET_ROUTE>;
829                         close(PROC_NET_ROUTE);
831                         # Eat header line
832                         shift @ifs;
833                         chomp @ifs;
834                         foreach my $line(@ifs) {
835                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
836                                 my $destination;
837                                 my $mask;
838                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
839                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
840                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
841                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
842                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
843                                         # destination matches route, save mac and exit
844                                         $result= &get_ip($Iface);
845                                         last;
846                                 }
847                         }
848                 }
849         } else {
850                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
851         }
852         return $result;
856 sub send_msg_to_target {
857     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
858     my $error = 0;
859     my $header;
860     my $new_status;
861     my $act_status;
862     my ($sql_statement, $res);
863   
864     if( $msg_header ) {
865         $header = "'$msg_header'-";
866     } else {
867         $header = "";
868     }
870         # Patch the source ip
871         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
872                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
873                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
874         }
876     # encrypt xml msg
877     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
879     # opensocket
880     my $socket = &open_socket($address);
881     if( !$socket ) {
882         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
883         $error++;
884     }
885     
886     if( $error == 0 ) {
887         # send xml msg
888         print $socket $crypted_msg."\n";
890         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
891         #daemon_log("DEBUG: message:\n$msg", 9);
892         
893     }
895     # close socket in any case
896     if( $socket ) {
897         close $socket;
898     }
900     if( $error > 0 ) { $new_status = "down"; }
901     else { $new_status = $msg_header; }
904     # known_clients
905     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
906     $res = $known_clients_db->select_dbentry($sql_statement);
907     if( keys(%$res) > 0) {
908         $act_status = $res->{1}->{'status'};
909         if( $act_status eq "down" ) {
910             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
911             $res = $known_clients_db->del_dbentry($sql_statement);
912             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
913         } else { 
914             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
915             $res = $known_clients_db->update_dbentry($sql_statement);
916             if($new_status eq "down"){
917                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
918             } else {
919                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
920             }
921         }
922     }
924     # known_server
925     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
926     $res = $known_server_db->select_dbentry($sql_statement);
927     if( keys(%$res) > 0 ) {
928         $act_status = $res->{1}->{'status'};
929         if( $act_status eq "down" ) {
930             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
931             $res = $known_server_db->del_dbentry($sql_statement);
932             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
933         } 
934         else { 
935             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
936             $res = $known_server_db->update_dbentry($sql_statement);
937             if($new_status eq "down"){
938                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
939             }
940             else {
941                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
942             }
943         }
944     }
945     return $error; 
949 sub update_jobdb_status_for_send_msgs {
950     my ($answer, $error) = @_;
951     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
952         my $jobdb_id = $1;
953             
954         # sending msg faild
955         if( $error ) {
956             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
957                 my $sql_statement = "UPDATE $job_queue_tn ".
958                     "SET status='error', result='can not deliver msg, please consult log file' ".
959                     "WHERE id='$jobdb_id'";
960                 my $res = $job_db->update_dbentry($sql_statement);
961             }
963         # sending msg was successful
964         } else {
965             my $sql_statement = "UPDATE $job_queue_tn ".
966                 "SET status='done' ".
967                 "WHERE id='$jobdb_id' AND status='processed'";
968             my $res = $job_db->update_dbentry($sql_statement);
969         }
970     }
973 sub _start {
974     my ($kernel) = $_[KERNEL];
975     &trigger_db_loop($kernel);
976     $global_kernel = $kernel;
977         $kernel->yield('create_fai_server_db', $fai_server_tn );
978         $kernel->yield('create_fai_release_db', $fai_release_tn );
979         $kernel->sig(USR1 => "sig_handler");
980         $kernel->sig(USR2 => "create_packages_list_db");
983 sub sig_handler {
984         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
985         daemon_log("0 INFO got signal '$signal'", 1); 
986         $kernel->sig_handled();
987         return;
990 sub next_task {
991     my ($session, $heap) = @_[SESSION, HEAP];
993     while ( keys( %{ $heap->{task} } ) < $max_children ) {
994         my $next_task = shift @tasks;
995         last unless defined $next_task;
997         my $task = POE::Wheel::Run->new(
998                 Program => sub { process_task($session, $heap, $next_task) },
999                 StdioFilter => POE::Filter::Reference->new(),
1000                 StdoutEvent  => "task_result",
1001                 StderrEvent  => "task_debug",
1002                 CloseEvent   => "task_done",
1003                );
1005         $heap->{task}->{ $task->ID } = $task;
1006     }
1009 sub handle_task_result {
1010     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1011     my $client_answer = $result->{'answer'};
1012     if( $client_answer =~ s/session_id=(\d+)$// ) {
1013         my $session_id = $1;
1014         if( defined $session_id ) {
1015             my $session_reference = $kernel->ID_id_to_session($session_id);
1016             if( defined $session_reference ) {
1017                 $heap = $session_reference->get_heap();
1018             }
1019         }
1021         if(exists $heap->{'client'}) {
1022             $heap->{'client'}->put($client_answer);
1023         }
1024     }
1025     $kernel->sig(CHLD => "child_reap");
1028 sub handle_task_debug {
1029     my $result = $_[ARG0];
1030     print STDERR "$result\n";
1033 sub handle_task_done {
1034     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1035     delete $heap->{task}->{$task_id};
1036     $kernel->yield("next_task");
1039 sub process_task {
1040     no strict "refs";
1041     my ($session, $heap, $input) = @_;
1042     my $session_id = $session->ID;
1043     my ($msg, $msg_hash, $module);
1044     my $error = 0;
1045     my $answer_l;
1046     my ($answer_header, @answer_target_l, $answer_source);
1047     my $client_answer = "";
1049     daemon_log("", 5); 
1050     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1051     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1053     ####################
1054     # check incoming msg
1055     # msg is from a new client or gosa
1056     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1057     # msg is from a gosa-si-server or gosa-si-bus
1058     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1059         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1060     }
1061     # msg is from a gosa-si-client
1062     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1063         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1064     }
1065     # an error occurred
1066     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1067         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1068         # could not understand a msg from its server the client cause a re-registering process
1069         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);
1070         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1071         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1072         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1073             my $host_name = $hit->{'hostname'};
1074             my $host_key = $hit->{'hostkey'};
1075             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1076             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1077             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1078         }
1079         $error++;
1080     }
1082     ######################
1083     # process incoming msg
1084     if( $error == 0) {
1085         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1086                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1087         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1088         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1090         if ( 0 < @{$answer_l} ) {
1091             my $answer_str = join("\n", @{$answer_l});
1092             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1093         } else {
1094             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1095         }
1097     }
1098     if( !$answer_l ) { $error++ };
1100     ########
1101     # answer
1102     if( $error == 0 ) {
1104         foreach my $answer ( @{$answer_l} ) {
1105             # for each answer in answer list
1106             
1107             # check outgoing msg to xml validity
1108             my $answer_hash = &check_outgoing_xml_validity($answer);
1109             if( not defined $answer_hash ) {
1110                 next;
1111             }
1112             
1113             $answer_header = @{$answer_hash->{'header'}}[0];
1114             @answer_target_l = @{$answer_hash->{'target'}};
1115             $answer_source = @{$answer_hash->{'source'}}[0];
1117             # deliver msg to all targets 
1118             foreach my $answer_target ( @answer_target_l ) {
1120                 # targets of msg are all gosa-si-clients in known_clients_db
1121                 if( $answer_target eq "*" ) {
1122                     # answer is for all clients
1123                     my $sql_statement= "SELECT * FROM known_clients";
1124                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1125                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1126                         my $host_name = $hit->{hostname};
1127                         my $host_key = $hit->{hostkey};
1128                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1129                         &update_jobdb_status_for_send_msgs($answer, $error);
1130                     }
1131                 }
1133                 # targets of msg are all gosa-si-server in known_server_db
1134                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1135                     # answer is for all server in known_server
1136                     my $sql_statement= "SELECT * FROM known_server";
1137                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1138                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1139                         my $host_name = $hit->{hostname};
1140                         my $host_key = $hit->{hostkey};
1141                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1142                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1143                         &update_jobdb_status_for_send_msgs($answer, $error);
1144                     }
1145                 }
1147                 # target of msg is GOsa
1148                                 elsif( $answer_target eq "GOSA" ) {
1149                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1150                                         my $add_on = "";
1151                     if( defined $session_id ) {
1152                         $add_on = ".session_id=$session_id";
1153                     }
1154                     # answer is for GOSA and has to returned to connected client
1155                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1156                     $client_answer = $gosa_answer.$add_on;
1157                 }
1159                 # target of msg is job queue at this host
1160                 elsif( $answer_target eq "JOBDB") {
1161                     $answer =~ /<header>(\S+)<\/header>/;   
1162                     my $header;
1163                     if( defined $1 ) { $header = $1; }
1164                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1165                     &update_jobdb_status_for_send_msgs($answer, $error);
1166                 }
1168                 # target of msg is a mac address
1169                 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 ) {
1170                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1171                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1172                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1173                     my $found_ip_flag = 0;
1174                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1175                         my $host_name = $hit->{hostname};
1176                         my $host_key = $hit->{hostkey};
1177                         $answer =~ s/$answer_target/$host_name/g;
1178                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1179                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1180                         &update_jobdb_status_for_send_msgs($answer, $error);
1181                         $found_ip_flag++ ;
1182                     }   
1183                     if( $found_ip_flag == 0) {
1184                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1185                         if( $bus_activ eq "true" ) { 
1186                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1187                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1188                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1189                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1190                                 my $bus_address = $hit->{hostname};
1191                                 my $bus_key = $hit->{hostkey};
1192                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1193                                 &update_jobdb_status_for_send_msgs($answer, $error);
1194                                 last;
1195                             }
1196                         }
1198                     }
1200                 #  answer is for one specific host   
1201                 } else {
1202                     # get encrypt_key
1203                     my $encrypt_key = &get_encrypt_key($answer_target);
1204                     if( not defined $encrypt_key ) {
1205                         # unknown target, forward msg to bus
1206                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1207                         if( $bus_activ eq "true" ) { 
1208                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1209                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1210                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1211                             my $res_length = keys( %{$query_res} );
1212                             if( $res_length == 0 ){
1213                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1214                                         "no bus found in known_server", 3);
1215                             }
1216                             else {
1217                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1218                                     my $bus_key = $hit->{hostkey};
1219                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1220                                     &update_jobdb_status_for_send_msgs($answer, $error);
1221                                 }
1222                             }
1223                         }
1224                         next;
1225                     }
1226                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1227                     &update_jobdb_status_for_send_msgs($answer, $error);
1228                 }
1229             }
1230         }
1231     }
1233     my $filter = POE::Filter::Reference->new();
1234     my %result = ( 
1235             status => "seems ok to me",
1236             answer => $client_answer,
1237             );
1239     my $output = $filter->put( [ \%result ] );
1240     print @$output;
1246 sub trigger_db_loop {
1247         my ($kernel) = @_ ;
1248         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1249         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1250     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1251     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1254 sub watch_for_done_jobs {
1255     my ($kernel,$heap) = @_[KERNEL, HEAP];
1257     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1258         " WHERE status='done'";
1259         my $res = $job_db->select_dbentry( $sql_statement );
1261     while( my ($id, $hit) = each %{$res} ) {
1262         my $jobdb_id = $hit->{id};
1263         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1264         my $res = $job_db->del_dbentry($sql_statement);
1265     }
1267     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1270 sub watch_for_new_jobs {
1271         my ($kernel,$heap) = @_[KERNEL, HEAP];
1273         # check gosa job queue for jobs with executable timestamp
1274         my $timestamp = &get_time();
1275         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1276         my $res = $job_db->exec_statement( $sql_statement );
1278         # Merge all new jobs that would do the same actions
1279         my @drops;
1280         my $hits;
1281         foreach my $hit (reverse @{$res} ) {
1282                 my $macaddress= lc @{$hit}[8];
1283                 my $headertag= @{$hit}[5];
1284                 if(
1285                         defined($hits->{$macaddress}) &&
1286                         defined($hits->{$macaddress}->{$headertag}) &&
1287                         defined($hits->{$macaddress}->{$headertag}[0])
1288                 ) {
1289                         push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1290                 }
1291                 $hits->{$macaddress}->{$headertag}= $hit;
1292         }
1294         # Delete new jobs with a matching job in state 'processing'
1295         foreach my $macaddress (keys %{$hits}) {
1296                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1297                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1298                         if(defined($jobdb_id)) {
1299                                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1300                                 my $res = $job_db->exec_statement( $sql_statement );
1301                                 foreach my $hit (@{$res}) {
1302                                         push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1303                                 }
1304                         } else {
1305                                 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1306                         }
1307                 }
1308         }
1310         # Commit deletion
1311         $job_db->exec_statementlist(\@drops);
1313         # Look for new jobs that could be executed
1314         foreach my $macaddress (keys %{$hits}) {
1316                 # Look if there is an executing job
1317                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1318                 my $res = $job_db->exec_statement( $sql_statement );
1320                 # Skip new jobs for host if there is a processing job
1321                 if(defined($res) and defined @{$res}[0]) {
1322                         next;
1323                 }
1325                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1326                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1327                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1329                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1330                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1331                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1333                         # expect macaddress is unique!!!!!!
1334                         my $target = $res_hash->{1}->{hostname};
1336                         # change header
1337                         $job_msg =~ s/<header>job_/<header>gosa_/;
1339                         # add sqlite_id
1340                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1342                         $job_msg =~ /<header>(\S+)<\/header>/;
1343                         my $header = $1 ;
1344                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1346                         # update status in job queue to 'processing'
1347                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1348                         my $res = $job_db->update_dbentry($sql_statement);
1350                         # We don't want parallel processing
1351                         last;
1352                 }
1353         }
1355         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1359 sub watch_for_new_messages {
1360     my ($kernel,$heap) = @_[KERNEL, HEAP];
1361     my @coll_user_msg;   # collection list of outgoing messages
1362     
1363     # check messaging_db for new incoming messages with executable timestamp
1364     my $timestamp = &get_time();
1365     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1366     my $res = $messaging_db->exec_statement( $sql_statement );
1367         foreach my $hit (@{$res}) {
1369         # create outgoing messages
1370         my $message_to = @{$hit}[3];
1372         # translate message_to to plain login name
1373 # TODO implement reciever translation
1374         my @reciever_l = ($message_to);  
1375         my $message_id = @{$hit}[0];
1377         #add each outgoing msg to messaging_db
1378         my $reciever;
1379         foreach $reciever (@reciever_l) {
1380             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1381                 "VALUES ('".
1382                 $message_id."', '".    # id
1383                 @{$hit}[1]."', '".     # subject
1384                 @{$hit}[2]."', '".     # message_from
1385                 $reciever."', '".      # message_to
1386                 "none"."', '".         # flag
1387                 "out"."', '".          # direction
1388                 @{$hit}[6]."', '".     # delivery_time
1389                 @{$hit}[7]."', '".     # message
1390                 $timestamp."'".     # timestamp
1391                 ")";
1392             &daemon_log("M DEBUG: $sql_statement", 1);
1393             my $res = $messaging_db->exec_statement($sql_statement);
1394             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to reciever '$reciever'", 5);
1395         }
1397         # send outgoing messages
1398         my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1399         my $res = $messaging_db->exec_statement( $sql_statement );
1400         foreach my $hit (@{$res}) {
1401             # add subject, from, to and message to list coll_user_msg
1402             my @user_msg = [@{$hit}[1], @{$hit}[2], $reciever, @{$hit}[7]];
1403             push( @coll_user_msg, \@user_msg);
1404         }
1406         # send outgoing list to myself (gosa-si-server) to deliver each message to user
1407         # reason for this workaround: if to much messages have to be delivered, it can come to 
1408         # denial of service problems of the server. so, the incoming message list can be processed
1409         # by a forked child and gosa-si-server is always ready to work. 
1410         my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1411         # add to hash 'msg1' => [subject, from, to, message]
1412         # hash to string
1413         # send msg to myself
1414 # TODO
1416         # set incoming message to flag d=deliverd
1417         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1418         &daemon_log("M DEBUG: $sql_statement", 7);
1419         $res = $messaging_db->update_dbentry($sql_statement);
1420         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1422     }
1423     
1424     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1427     return;
1431 sub watch_for_done_messages {
1432     my ($kernel,$heap) = @_[KERNEL, HEAP];
1434     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1435     return;
1439 sub get_ldap_handle {
1440         my ($session_id) = @_;
1441         my $heap;
1442         my $ldap_handle;
1444         if (not defined $session_id ) { $session_id = 0 };
1445         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1447         if ($session_id == 0) {
1448                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1449                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1450                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1452         } else {
1453                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1454                 if( defined $session_reference ) {
1455                         $heap = $session_reference->get_heap();
1456                 }
1458                 if (not defined $heap) {
1459                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1460                         return;
1461                 }
1463                 # TODO: This "if" is nonsense, because it doesn't prove that the
1464                 #       used handle is still valid - or if we've to reconnect...
1465                 #if (not exists $heap->{ldap_handle}) {
1466                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1467                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1468                         $heap->{ldap_handle} = $ldap_handle;
1469                 #}
1470         }
1471         return $ldap_handle;
1475 sub change_fai_state {
1476     my ($st, $targets, $session_id) = @_;
1477     $session_id = 0 if not defined $session_id;
1478     # Set FAI state to localboot
1479     my %mapActions= (
1480         reboot    => '',
1481         update    => 'softupdate',
1482         localboot => 'localboot',
1483         reinstall => 'install',
1484         rescan    => '',
1485         wake      => '',
1486         memcheck  => 'memcheck',
1487         sysinfo   => 'sysinfo',
1488         install   => 'install',
1489     );
1491     # Return if this is unknown
1492     if (!exists $mapActions{ $st }){
1493         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1494       return;
1495     }
1497     my $state= $mapActions{ $st };
1499     my $ldap_handle = &get_ldap_handle($session_id);
1500     if( defined($ldap_handle) ) {
1502       # Build search filter for hosts
1503         my $search= "(&(objectClass=GOhard)";
1504         foreach (@{$targets}){
1505             $search.= "(macAddress=$_)";
1506         }
1507         $search.= ")";
1509       # If there's any host inside of the search string, procress them
1510         if (!($search =~ /macAddress/)){
1511             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1512             return;
1513         }
1515       # Perform search for Unit Tag
1516       my $mesg = $ldap_handle->search(
1517           base   => $ldap_base,
1518           scope  => 'sub',
1519           attrs  => ['dn', 'FAIstate', 'objectClass'],
1520           filter => "$search"
1521           );
1523           if ($mesg->count) {
1524                   my @entries = $mesg->entries;
1525                   foreach my $entry (@entries) {
1526                           # Only modify entry if it is not set to '$state'
1527                           if ($entry->get_value("FAIstate") ne "$state"){
1528                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1529                                   my $result;
1530                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1531                                   if (exists $tmp{'FAIobject'}){
1532                                           if ($state eq ''){
1533                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1534                                                           delete => [ FAIstate => [] ] ]);
1535                                           } else {
1536                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1537                                                           replace => [ FAIstate => $state ] ]);
1538                                           }
1539                                   } elsif ($state ne ''){
1540                                           $result= $ldap_handle->modify($entry->dn, changes => [
1541                                                   add     => [ objectClass => 'FAIobject' ],
1542                                                   add     => [ FAIstate => $state ] ]);
1543                                   }
1545                                   # Errors?
1546                                   if ($result->code){
1547                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1548                                   }
1549                           } else {
1550                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1551                           }  
1552                   }
1553           }
1554     # if no ldap handle defined
1555     } else {
1556         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1557     }
1562 sub change_goto_state {
1563     my ($st, $targets, $session_id) = @_;
1564     $session_id = 0  if not defined $session_id;
1566     # Switch on or off?
1567     my $state= $st eq 'active' ? 'active': 'locked';
1569     my $ldap_handle = &get_ldap_handle($session_id);
1570     if( defined($ldap_handle) ) {
1572       # Build search filter for hosts
1573       my $search= "(&(objectClass=GOhard)";
1574       foreach (@{$targets}){
1575         $search.= "(macAddress=$_)";
1576       }
1577       $search.= ")";
1579       # If there's any host inside of the search string, procress them
1580       if (!($search =~ /macAddress/)){
1581         return;
1582       }
1584       # Perform search for Unit Tag
1585       my $mesg = $ldap_handle->search(
1586           base   => $ldap_base,
1587           scope  => 'sub',
1588           attrs  => ['dn', 'gotoMode'],
1589           filter => "$search"
1590           );
1592       if ($mesg->count) {
1593         my @entries = $mesg->entries;
1594         foreach my $entry (@entries) {
1596           # Only modify entry if it is not set to '$state'
1597           if ($entry->get_value("gotoMode") ne $state){
1599             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1600             my $result;
1601             $result= $ldap_handle->modify($entry->dn, changes => [
1602                                                 replace => [ gotoMode => $state ] ]);
1604             # Errors?
1605             if ($result->code){
1606               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1607             }
1609           }
1610         }
1611       }
1613     }
1617 sub run_create_fai_server_db {
1618     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1619     my $session_id = $session->ID;
1620     my $task = POE::Wheel::Run->new(
1621             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1622             StdoutEvent  => "session_run_result",
1623             StderrEvent  => "session_run_debug",
1624             CloseEvent   => "session_run_done",
1625             );
1627     $heap->{task}->{ $task->ID } = $task;
1628     return;
1632 sub create_fai_server_db {
1633     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1634         my $result;
1636         if (not defined $session_id) { $session_id = 0; }
1637     my $ldap_handle = &get_ldap_handle();
1638         if(defined($ldap_handle)) {
1639                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1640                 my $mesg= $ldap_handle->search(
1641                         base   => $ldap_base,
1642                         scope  => 'sub',
1643                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1644                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1645                 );
1646                 if($mesg->{'resultCode'} == 0 &&
1647                    $mesg->count != 0) {
1648                    foreach my $entry (@{$mesg->{entries}}) {
1649                            if($entry->exists('FAIrepository')) {
1650                                    # Add an entry for each Repository configured for server
1651                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1652                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1653                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1654                                                    $result= $fai_server_db->add_dbentry( { 
1655                                                                    table => $table_name,
1656                                                                    primkey => ['server', 'release', 'tag'],
1657                                                                    server => $tmp_url,
1658                                                                    release => $tmp_release,
1659                                                                    sections => $tmp_sections,
1660                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1661                                                            } );
1662                                            }
1663                                    }
1664                            }
1665                    }
1666                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1668                 # TODO: Find a way to post the 'create_packages_list_db' event
1669                 if(not defined($dont_create_packages_list)) {
1670                         &create_packages_list_db(undef, undef, $session_id);
1671                 }
1672         }       
1673     
1674     $ldap_handle->disconnect;
1675         return $result;
1679 sub run_create_fai_release_db {
1680     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1681         my $session_id = $session->ID;
1682     my $task = POE::Wheel::Run->new(
1683             Program => sub { &create_fai_release_db($table_name, $session_id) },
1684             StdoutEvent  => "session_run_result",
1685             StderrEvent  => "session_run_debug",
1686             CloseEvent   => "session_run_done",
1687             );
1689     $heap->{task}->{ $task->ID } = $task;
1690     return;
1694 sub create_fai_release_db {
1695         my ($table_name, $session_id) = @_;
1696         my $result;
1698     # used for logging
1699     if (not defined $session_id) { $session_id = 0; }
1701     my $ldap_handle = &get_ldap_handle();
1702         if(defined($ldap_handle)) {
1703                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1704                 my $mesg= $ldap_handle->search(
1705                         base   => $ldap_base,
1706                         scope  => 'sub',
1707                         attrs  => [],
1708                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1709                 );
1710                 if($mesg->{'resultCode'} == 0 &&
1711                         $mesg->count != 0) {
1712                         # Walk through all possible FAI container ou's
1713                         my @sql_list;
1714                         my $timestamp= &get_time();
1715                         foreach my $ou (@{$mesg->{entries}}) {
1716                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1717                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1718                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1719                                         if(@tmp_array) {
1720                                                 foreach my $entry (@tmp_array) {
1721                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1722                                                                 my $sql= 
1723                                                                 "INSERT INTO $table_name "
1724                                                                 ."(timestamp, release, class, type, state) VALUES ("
1725                                                                 .$timestamp.","
1726                                                                 ."'".$entry->{'release'}."',"
1727                                                                 ."'".$entry->{'class'}."',"
1728                                                                 ."'".$entry->{'type'}."',"
1729                                                                 ."'".$entry->{'state'}."')";
1730                                                                 push @sql_list, $sql;
1731                                                         }
1732                                                 }
1733                                         }
1734                                 }
1735                         }
1737                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1738                         if(@sql_list) {
1739                                 unshift @sql_list, "DELETE FROM $table_name";   # at first, clear db
1740                                 $fai_release_db->exec_statementlist(\@sql_list);
1741                         }
1742                         daemon_log("$session_id DEBUG: Done with inserting",7);
1743                 }
1744                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1745         }
1746     $ldap_handle->disconnect;
1747         return $result;
1750 sub get_fai_types {
1751         my $tmp_classes = shift || return undef;
1752         my @result;
1754         foreach my $type(keys %{$tmp_classes}) {
1755                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1756                         my $entry = {
1757                                 type => $type,
1758                                 state => $tmp_classes->{$type}[0],
1759                         };
1760                         push @result, $entry;
1761                 }
1762         }
1764         return @result;
1767 sub get_fai_state {
1768         my $result = "";
1769         my $tmp_classes = shift || return $result;
1771         foreach my $type(keys %{$tmp_classes}) {
1772                 if(defined($tmp_classes->{$type}[0])) {
1773                         $result = $tmp_classes->{$type}[0];
1774                         
1775                 # State is equal for all types in class
1776                         last;
1777                 }
1778         }
1780         return $result;
1783 sub resolve_fai_classes {
1784         my ($fai_base, $ldap_handle, $session_id) = @_;
1785         if (not defined $session_id) { $session_id = 0; }
1786         my $result;
1787         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1788         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1789         my $fai_classes;
1791         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1792         my $mesg= $ldap_handle->search(
1793                 base   => $fai_base,
1794                 scope  => 'sub',
1795                 attrs  => ['cn','objectClass','FAIstate'],
1796                 filter => $fai_filter,
1797         );
1798         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1800         if($mesg->{'resultCode'} == 0 &&
1801                 $mesg->count != 0) {
1802                 foreach my $entry (@{$mesg->{entries}}) {
1803                         if($entry->exists('cn')) {
1804                                 my $tmp_dn= $entry->dn();
1806                                 # Skip classname and ou dn parts for class
1807                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1809                                 # Skip classes without releases
1810                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1811                                         next;
1812                                 }
1814                                 my $tmp_cn= $entry->get_value('cn');
1815                                 my $tmp_state= $entry->get_value('FAIstate');
1817                                 my $tmp_type;
1818                                 # Get FAI type
1819                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1820                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1821                                                 $tmp_type= $oclass;
1822                                                 last;
1823                                         }
1824                                 }
1826                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1827                                         # A Subrelease
1828                                         my @sub_releases = split(/,/, $tmp_release);
1830                                         # Walk through subreleases and build hash tree
1831                                         my $hash;
1832                                         while(my $tmp_sub_release = pop @sub_releases) {
1833                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1834                                         }
1835                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1836                                 } else {
1837                                         # A branch, no subrelease
1838                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1839                                 }
1840                         } elsif (!$entry->exists('cn')) {
1841                                 my $tmp_dn= $entry->dn();
1842                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1844                                 # Skip classes without releases
1845                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1846                                         next;
1847                                 }
1849                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1850                                         # A Subrelease
1851                                         my @sub_releases= split(/,/, $tmp_release);
1853                                         # Walk through subreleases and build hash tree
1854                                         my $hash;
1855                                         while(my $tmp_sub_release = pop @sub_releases) {
1856                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1857                                         }
1858                                         # Remove the last two characters
1859                                         chop($hash);
1860                                         chop($hash);
1862                                         eval('$fai_classes->'.$hash.'= {}');
1863                                 } else {
1864                                         # A branch, no subrelease
1865                                         if(!exists($fai_classes->{$tmp_release})) {
1866                                                 $fai_classes->{$tmp_release} = {};
1867                                         }
1868                                 }
1869                         }
1870                 }
1872                 # The hash is complete, now we can honor the copy-on-write based missing entries
1873                 foreach my $release (keys %$fai_classes) {
1874                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1875                 }
1876         }
1877         return $result;
1880 sub apply_fai_inheritance {
1881        my $fai_classes = shift || return {};
1882        my $tmp_classes;
1884        # Get the classes from the branch
1885        foreach my $class (keys %{$fai_classes}) {
1886                # Skip subreleases
1887                if($class =~ /^ou=.*$/) {
1888                        next;
1889                } else {
1890                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1891                }
1892        }
1894        # Apply to each subrelease
1895        foreach my $subrelease (keys %{$fai_classes}) {
1896                if($subrelease =~ /ou=/) {
1897                        foreach my $tmp_class (keys %{$tmp_classes}) {
1898                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1899                                        $fai_classes->{$subrelease}->{$tmp_class} =
1900                                        deep_copy($tmp_classes->{$tmp_class});
1901                                } else {
1902                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1903                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1904                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1905                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1906                                                }
1907                                        }
1908                                }
1909                        }
1910                }
1911        }
1913        # Find subreleases in deeper levels
1914        foreach my $subrelease (keys %{$fai_classes}) {
1915                if($subrelease =~ /ou=/) {
1916                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1917                                if($subsubrelease =~ /ou=/) {
1918                                        apply_fai_inheritance($fai_classes->{$subrelease});
1919                                }
1920                        }
1921                }
1922        }
1924        return $fai_classes;
1927 sub get_fai_release_entries {
1928         my $tmp_classes = shift || return;
1929         my $parent = shift || "";
1930         my @result = shift || ();
1932         foreach my $entry (keys %{$tmp_classes}) {
1933                 if(defined($entry)) {
1934                         if($entry =~ /^ou=.*$/) {
1935                                 my $release_name = $entry;
1936                                 $release_name =~ s/ou=//g;
1937                                 if(length($parent)>0) {
1938                                         $release_name = $parent."/".$release_name;
1939                                 }
1940                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1941                                 foreach my $bufentry(@bufentries) {
1942                                         push @result, $bufentry;
1943                                 }
1944                         } else {
1945                                 my @types = get_fai_types($tmp_classes->{$entry});
1946                                 foreach my $type (@types) {
1947                                         push @result, 
1948                                         {
1949                                                 'class' => $entry,
1950                                                 'type' => $type->{'type'},
1951                                                 'release' => $parent,
1952                                                 'state' => $type->{'state'},
1953                                         };
1954                                 }
1955                         }
1956                 }
1957         }
1959         return @result;
1962 sub deep_copy {
1963         my $this = shift;
1964         if (not ref $this) {
1965                 $this;
1966         } elsif (ref $this eq "ARRAY") {
1967                 [map deep_copy($_), @$this];
1968         } elsif (ref $this eq "HASH") {
1969                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1970         } else { die "what type is $_?" }
1974 sub session_run_result {
1975     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1976     $kernel->sig(CHLD => "child_reap");
1979 sub session_run_debug {
1980     my $result = $_[ARG0];
1981     print STDERR "$result\n";
1984 sub session_run_done {
1985     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1986     delete $heap->{task}->{$task_id};
1989 sub create_sources_list {
1990     my ($session_id) = @_;
1991     my $ldap_handle = &get_ldap_handle;
1992         my $result="/tmp/gosa_si_tmp_sources_list";
1994         # Remove old file
1995         if(stat($result)) {
1996                 unlink($result);
1997         &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
1998         }
2000         my $fh;
2001         open($fh, ">$result");
2002     if (not defined $fh) {
2003         &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2004         return undef;
2005     }
2006         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
2007                 my $mesg=$ldap_handle->search(
2008                                 base    => $ldap_server_dn,
2009                                 scope   => 'base',
2010                                 attrs   => 'FAIrepository',
2011                                 filter  => 'objectClass=FAIrepositoryServer'
2012                                 );
2013                 if($mesg->count) {
2014                         foreach my $entry(@{$mesg->{'entries'}}) {
2015                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2016                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2017                                         my $line = "deb $server $release";
2018                                         $sections =~ s/,/ /g;
2019                                         $line.= " $sections";
2020                                         print $fh $line."\n";
2021                                 }
2022                         }
2023                 }
2024         } else {
2025         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$ldap_server_dn', abort create_sources_list", 1); 
2026     }
2027         close($fh);
2029         return $result;
2033 sub run_create_packages_list_db {
2034     my ($session, $heap) = @_[SESSION, HEAP];
2035         my $session_id = $session->ID;
2037         my $task = POE::Wheel::Run->new(
2038                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2039                                         StdoutEvent  => "session_run_result",
2040                                         StderrEvent  => "session_run_debug",
2041                                         CloseEvent   => "session_run_done",
2042                                         );
2043         $heap->{task}->{ $task->ID } = $task;
2047 sub create_packages_list_db {
2048     my ($ldap_handle, $sources_file, $session_id) = @_;
2050         if (not defined $session_id) { $session_id = 0; }
2051         if (not defined $ldap_handle) { 
2052                 $ldap_handle= &get_ldap_handle();
2054                 if (not defined $ldap_handle) {
2055                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2056                         return;
2057                 }
2058         }
2059     if (not defined $sources_file) { 
2060         &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2061         $sources_file = &create_sources_list($session_id);
2062     }
2064     if (not defined $sources_file) {
2065         &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2067     }
2069         # it should not be possible to trigger a recreation of packages_list_db
2070         # while packages_list_db is under construction, so set flag packages_list_under_construction
2071         # which is tested befor recreation can be started
2072         if ($packages_list_under_construction) {
2073                         daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait untill this process is finished", 3);
2074                         return;
2075         } else {
2076                         daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2077                         # set packages_list_under_construction to true
2078                         $packages_list_under_construction = 1;
2079         }
2080         my $line;
2082     open(CONFIG, "<$sources_file") or do {
2083         daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2084         return;
2085     };
2086     
2087     # Read lines
2088     while ($line = <CONFIG>){
2089         # Unify
2090         chop($line);
2091         $line =~ s/^\s+//;
2092         $line =~ s/^\s+/ /;
2094         # Strip comments
2095         $line =~ s/#.*$//g;
2097         # Skip empty lines
2098         if ($line =~ /^\s*$/){
2099             next;
2100         }
2102         # Interpret deb line
2103         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2104             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2105             my $section;
2106             foreach $section (split(' ', $sections)){
2107                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2108             }
2109         }
2110     }
2112     close (CONFIG);
2114     daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2115         # set packages_list_under_construction to false
2116         $packages_list_under_construction = 0;
2118     return;
2122 sub parse_package_info {
2123     my ($baseurl, $dist, $section, $session_id)= @_;
2124     my ($package);
2125     if (not defined $session_id) { $session_id = 0; }
2126     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2127     $repo_dirs{ "${repo_path}/pool" } = 1;
2129     foreach $package ("Packages.gz"){
2130         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2131         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2132         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2133     }
2134     
2135     find(\&cleanup_and_extract, keys( %repo_dirs ));
2139 sub get_package {
2140     my ($url, $dest, $session_id)= @_;
2141     if (not defined $session_id) { $session_id = 0; }
2143     my $tpath = dirname($dest);
2144     -d "$tpath" || mkpath "$tpath";
2146     # This is ugly, but I've no time to take a look at "how it works in perl"
2147     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2148         system("gunzip -cd '$dest' > '$dest.in'");
2149         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2150         unlink($dest);
2151         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2152     } else {
2153         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2154     }
2155     return 0;
2159 sub parse_package {
2160     my ($path, $dist, $srv_path, $session_id)= @_;
2161     if (not defined $session_id) { $session_id = 0;}
2162     my ($package, $version, $section, $description);
2163     my @sql_list;
2164     my $PACKAGES;
2165     my $timestamp = &get_time();
2167     if(not stat("$path.in")) {
2168         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2169         return;
2170     }
2172     open($PACKAGES, "<$path.in");
2173     if(not defined($PACKAGES)) {
2174         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2175         return;
2176     }
2178     # Read lines
2179     while (<$PACKAGES>){
2180         my $line = $_;
2181         # Unify
2182         chop($line);
2184         # Use empty lines as a trigger
2185         if ($line =~ /^\s*$/){
2186             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '$timestamp')";
2187             push(@sql_list, $sql);
2188             $package = "none";
2189             $version = "none";
2190             $section = "none";
2191             $description = "none"; 
2192             next;
2193         }
2195         # Trigger for package name
2196         if ($line =~ /^Package:\s/){
2197             ($package)= ($line =~ /^Package: (.*)$/);
2198             next;
2199         }
2201         # Trigger for version
2202         if ($line =~ /^Version:\s/){
2203             ($version)= ($line =~ /^Version: (.*)$/);
2204             next;
2205         }
2207         # Trigger for description
2208         if ($line =~ /^Description:\s/){
2209             ($description)= ($line =~ /^Description: (.*)$/);
2210             next;
2211         }
2213         # Trigger for section
2214         if ($line =~ /^Section:\s/){
2215             ($section)= ($line =~ /^Section: (.*)$/);
2216             next;
2217         }
2219         # Trigger for filename
2220         if ($line =~ /^Filename:\s/){
2221             my ($filename) = ($line =~ /^Filename: (.*)$/);
2222             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2223             next;
2224         }
2225     }
2227     close( $PACKAGES );
2228     unlink( "$path.in" );
2229     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2230     my $len_sql_list = @sql_list;
2231     &main::daemon_log("$session_id DEBUG: add $len_sql_list insert-statements to packages_list_db", 5); 
2232     $packages_list_db->exec_statementlist(\@sql_list);
2236 sub store_fileinfo {
2237     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2239     my %fileinfo = (
2240         'package' => $package,
2241         'dist' => $dist,
2242         'version' => $vers,
2243     );
2245     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2249 sub cleanup_and_extract {
2250     my $fileinfo = $repo_files{ $File::Find::name };
2252     if( defined $fileinfo ) {
2254         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2255         my $sql;
2256         my $package = $fileinfo->{ 'package' };
2257         my $newver = $fileinfo->{ 'version' };
2259         mkpath($dir);
2260         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2262         if( -f "$dir/DEBIAN/templates" ) {
2264             daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2266             my $tmpl= "";
2267             {
2268                 local $/=undef;
2269                 open FILE, "$dir/DEBIAN/templates";
2270                 $tmpl = &encode_base64(<FILE>);
2271                 close FILE;
2272             }
2273             rmtree("$dir/DEBIAN/templates");
2275             $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2277         } else {
2278             $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2279         }
2281         my $res= $main::packages_list_db->update_dbentry($sql);
2282     }
2284     return;
2288 #==== MAIN = main ==============================================================
2289 #  parse commandline options
2290 Getopt::Long::Configure( "bundling" );
2291 GetOptions("h|help" => \&usage,
2292         "c|config=s" => \$cfg_file,
2293         "f|foreground" => \$foreground,
2294         "v|verbose+" => \$verbose,
2295         "no-bus+" => \$no_bus,
2296         "no-arp+" => \$no_arp,
2297            );
2299 #  read and set config parameters
2300 &check_cmdline_param ;
2301 &read_configfile;
2302 &check_pid;
2304 $SIG{CHLD} = 'IGNORE';
2306 # forward error messages to logfile
2307 if( ! $foreground ) {
2308   open( STDIN,  '+>/dev/null' );
2309   open( STDOUT, '+>&STDIN'    );
2310   open( STDERR, '+>&STDIN'    );
2313 # Just fork, if we are not in foreground mode
2314 if( ! $foreground ) { 
2315     chdir '/'                 or die "Can't chdir to /: $!";
2316     $pid = fork;
2317     setsid                    or die "Can't start a new session: $!";
2318     umask 0;
2319 } else { 
2320     $pid = $$; 
2323 # Do something useful - put our PID into the pid_file
2324 if( 0 != $pid ) {
2325     open( LOCK_FILE, ">$pid_file" );
2326     print LOCK_FILE "$pid\n";
2327     close( LOCK_FILE );
2328     if( !$foreground ) { 
2329         exit( 0 ) 
2330     };
2333 daemon_log(" ", 1);
2334 daemon_log("$0 started!", 1);
2336 if ($no_bus > 0) {
2337     $bus_activ = "false"
2340 # connect to gosa-si job queue
2341 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2342 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2344 # connect to known_clients_db
2345 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2346 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2348 # connect to known_server_db
2349 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2350 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2352 # connect to login_usr_db
2353 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2354 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2356 # connect to fai_server_db and fai_release_db
2357 unlink($fai_server_file_name);
2358 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2359 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2361 unlink($fai_release_file_name);
2362 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2363 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2365 # connect to packages_list_db
2366 unlink($packages_list_file_name);
2367 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2368 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2370 # connect to messaging_db
2371 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2372 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2375 # create xml object used for en/decrypting
2376 $xml = new XML::Simple();
2378 # create socket for incoming xml messages
2380 POE::Component::Server::TCP->new(
2381         Port => $server_port,
2382         ClientInput => sub {
2383         my ($kernel, $input) = @_[KERNEL, ARG0];
2384         push(@tasks, $input);
2385         $kernel->yield("next_task");
2386         },
2387     InlineStates => {
2388         next_task => \&next_task,
2389         task_result => \&handle_task_result,
2390         task_done   => \&handle_task_done,
2391         task_debug  => \&handle_task_debug,
2392         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2393     }
2394 );
2396 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2398 # create session for repeatedly checking the job queue for jobs
2399 POE::Session->create(
2400         inline_states => {
2401                 _start => \&_start,
2402                 sig_handler => \&sig_handler,
2403         watch_for_new_messages => \&watch_for_new_messages,
2404         watch_for_done_messages => \&watch_for_done_messages,
2405                 watch_for_new_jobs => \&watch_for_new_jobs,
2406         watch_for_done_jobs => \&watch_for_done_jobs,
2407         create_packages_list_db => \&run_create_packages_list_db,
2408         create_fai_server_db => \&run_create_fai_server_db,
2409         create_fai_release_db => \&run_create_fai_release_db,
2410         session_run_result => \&session_run_result,
2411         session_run_debug => \&session_run_debug,
2412         session_run_done => \&session_run_done,
2413         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2414         }
2415 );
2418 # import all modules
2419 &import_modules;
2421 # check wether all modules are gosa-si valid passwd check
2423 POE::Kernel->run();
2424 exit;