Code

cbf152842b36b94f2682386e6bef3f50c83b9602
[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 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file, 
86     $server_ip, $server_port, $ClientPackages_key, 
87     $arp_activ, $gosa_unit_tag,
88     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
104 # Where should new systems be placed
105 our $new_systems_ou;
107 # specifies the verbosity of the daemon_log
108 $verbose = 0 ;
110 # if foreground is not null, script will be not forked to background
111 $foreground = 0 ;
113 # specifies the timeout seconds while checking the online status of a registrating client
114 $ping_timeout = 5;
116 $no_bus = 0;
117 $bus_activ = "true";
118 $no_arp = 0;
119 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
120 my @packages_list_statements;
121 my $watch_for_new_jobs_in_progress = 0;
123 # holds all incoming decrypted messages
124 our $incoming_db;
125 our $incoming_tn = 'incoming';
126 my $incoming_file_name;
127 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
128         "timestamp DEFAULT 'none'", 
129         "headertag DEFAULT 'none'",
130                 "targettag DEFAULT 'none'",
131         "xmlmessage DEFAULT 'none'",
132         "module DEFAULT 'none'",
133         "sessionid DEFAULT '0'",
134         );
136 # holds all gosa jobs
137 our $job_db;
138 our $job_queue_tn = 'jobs';
139 my $job_queue_file_name;
140 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
141                 "timestamp DEFAULT 'none'", 
142                 "status DEFAULT 'none'", 
143                 "result DEFAULT 'none'", 
144                 "progress DEFAULT 'none'", 
145         "headertag DEFAULT 'none'", 
146                 "targettag DEFAULT 'none'", 
147                 "xmlmessage DEFAULT 'none'", 
148                 "macaddress DEFAULT 'none'",
149                 "plainname DEFAULT 'none'",
150                 );
152 # holds all other gosa-sd as well as the gosa-sd-bus
153 our $known_server_db;
154 our $known_server_tn = "known_server";
155 my $known_server_file_name;
156 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
158 # holds all registrated clients
159 our $known_clients_db;
160 our $known_clients_tn = "known_clients";
161 my $known_clients_file_name;
162 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
164 # holds all registered clients at a foreign server
165 our $foreign_clients_db;
166 our $foreign_clients_tn = "foreign_clients"; 
167 my $foreign_clients_file_name;
168 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
170 # holds all logged in user at each client 
171 our $login_users_db;
172 our $login_users_tn = "login_users";
173 my $login_users_file_name;
174 my @login_users_col_names = ("client", "user", "timestamp");
176 # holds all fai server, the debian release and tag
177 our $fai_server_db;
178 our $fai_server_tn = "fai_server"; 
179 my $fai_server_file_name;
180 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
182 our $fai_release_db;
183 our $fai_release_tn = "fai_release"; 
184 my $fai_release_file_name;
185 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
187 # holds all packages available from different repositories
188 our $packages_list_db;
189 our $packages_list_tn = "packages_list";
190 my $packages_list_file_name;
191 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
192 my $outdir = "/tmp/packages_list_db";
193 my $arch = "i386"; 
195 # holds all messages which should be delivered to a user
196 our $messaging_db;
197 our $messaging_tn = "messaging"; 
198 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
199         "flag", "direction", "delivery_time", "message", "timestamp" );
200 my $messaging_file_name;
202 # path to directory to store client install log files
203 our $client_fai_log_dir = "/var/log/fai"; 
205 # queue which stores taskes until one of the $max_children children are ready to process the task
206 my @tasks = qw();
207 my @msgs_to_decrypt = qw();
208 my $max_children = 2;
211 %cfg_defaults = (
212 "general" => {
213     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
214     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
215     },
216 "bus" => {
217     "activ" => [\$bus_activ, "true"],
218     },
219 "server" => {
220     "port" => [\$server_port, "20081"],
221     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
222     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
223     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
224     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
225     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
226     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
227     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
228     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
229     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
230     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
231     "repo-path"            => [\$repo_path, '/srv/www/repository'],
232     "ldap-uri"             => [\$ldap_uri, ""],
233     "ldap-base"            => [\$ldap_base, ""],
234     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
235     "ldap-admin-password"  => [\$ldap_admin_password, ""],
236     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
237     "max-clients"          => [\$max_clients, 10],
238     },
239 "GOsaPackages" => {
240     "ip" => [\$gosa_ip, "0.0.0.0"],
241     "port" => [\$gosa_port, "20082"],
242     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
243     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
244     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
245     "key" => [\$GosaPackages_key, "none"],
246         "new-systems-ou" => [\$new_systems_ou, "ou=systems"],
247     },
248 "ClientPackages" => {
249     "key" => [\$ClientPackages_key, "none"],
250     },
251 "ServerPackages"=> {
252     "address"      => [\$foreign_server_string, ""],
253     "domain"  => [\$server_domain, ""],
254     "key"     => [\$ServerPackages_key, "none"],
255     "key-lifetime" => [\$foreign_servers_register_delay, 120],
257 );
260 #===  FUNCTION  ================================================================
261 #         NAME:  usage
262 #   PARAMETERS:  nothing
263 #      RETURNS:  nothing
264 #  DESCRIPTION:  print out usage text to STDERR
265 #===============================================================================
266 sub usage {
267     print STDERR << "EOF" ;
268 usage: $prg [-hvf] [-c config]
270            -h        : this (help) message
271            -c <file> : config file
272            -f        : foreground, process will not be forked to background
273            -v        : be verbose (multiple to increase verbosity)
274            -no-bus   : starts $prg without connection to bus
275            -no-arp   : starts $prg without connection to arp module
276  
277 EOF
278     print "\n" ;
282 #===  FUNCTION  ================================================================
283 #         NAME:  read_configfile
284 #   PARAMETERS:  cfg_file - string -
285 #      RETURNS:  nothing
286 #  DESCRIPTION:  read cfg_file and set variables
287 #===============================================================================
288 sub read_configfile {
289     my $cfg;
290     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
291         if( -r $cfg_file ) {
292             $cfg = Config::IniFiles->new( -file => $cfg_file );
293         } else {
294             print STDERR "Couldn't read config file!\n";
295         }
296     } else {
297         $cfg = Config::IniFiles->new() ;
298     }
299     foreach my $section (keys %cfg_defaults) {
300         foreach my $param (keys %{$cfg_defaults{ $section }}) {
301             my $pinfo = $cfg_defaults{ $section }{ $param };
302             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
303         }
304     }
308 #===  FUNCTION  ================================================================
309 #         NAME:  logging
310 #   PARAMETERS:  level - string - default 'info'
311 #                msg - string -
312 #                facility - string - default 'LOG_DAEMON'
313 #      RETURNS:  nothing
314 #  DESCRIPTION:  function for logging
315 #===============================================================================
316 sub daemon_log {
317     # log into log_file
318     my( $msg, $level ) = @_;
319     if(not defined $msg) { return }
320     if(not defined $level) { $level = 1 }
321     if(defined $log_file){
322         open(LOG_HANDLE, ">>$log_file");
323         chmod 0600, $log_file;
324         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
325             print STDERR "cannot open $log_file: $!";
326             return 
327         }
328         chomp($msg);
329         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
330         if($level <= $verbose){
331             my ($seconds, $minutes, $hours, $monthday, $month,
332                     $year, $weekday, $yearday, $sommertime) = localtime(time);
333             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
334             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
335             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
336             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
337             $month = $monthnames[$month];
338             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
339             $year+=1900;
340             my $name = $prg;
342             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
343             print LOG_HANDLE $log_msg;
344             if( $foreground ) { 
345                 print STDERR $log_msg;
346             }
347         }
348         close( LOG_HANDLE );
349     }
353 #===  FUNCTION  ================================================================
354 #         NAME:  check_cmdline_param
355 #   PARAMETERS:  nothing
356 #      RETURNS:  nothing
357 #  DESCRIPTION:  validates commandline parameter
358 #===============================================================================
359 sub check_cmdline_param () {
360     my $err_config;
361     my $err_counter = 0;
362         if(not defined($cfg_file)) {
363                 $cfg_file = "/etc/gosa-si/server.conf";
364                 if(! -r $cfg_file) {
365                         $err_config = "please specify a config file";
366                         $err_counter += 1;
367                 }
368     }
369     if( $err_counter > 0 ) {
370         &usage( "", 1 );
371         if( defined( $err_config)) { print STDERR "$err_config\n"}
372         print STDERR "\n";
373         exit( -1 );
374     }
378 #===  FUNCTION  ================================================================
379 #         NAME:  check_pid
380 #   PARAMETERS:  nothing
381 #      RETURNS:  nothing
382 #  DESCRIPTION:  handels pid processing
383 #===============================================================================
384 sub check_pid {
385     $pid = -1;
386     # Check, if we are already running
387     if( open(LOCK_FILE, "<$pid_file") ) {
388         $pid = <LOCK_FILE>;
389         if( defined $pid ) {
390             chomp( $pid );
391             if( -f "/proc/$pid/stat" ) {
392                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
393                 if( $stat ) {
394                                         daemon_log("ERROR: Already running",1);
395                     close( LOCK_FILE );
396                     exit -1;
397                 }
398             }
399         }
400         close( LOCK_FILE );
401         unlink( $pid_file );
402     }
404     # create a syslog msg if it is not to possible to open PID file
405     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
406         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
407         if (open(LOCK_FILE, '<', $pid_file)
408                 && ($pid = <LOCK_FILE>))
409         {
410             chomp($pid);
411             $msg .= "(PID $pid)\n";
412         } else {
413             $msg .= "(unable to read PID)\n";
414         }
415         if( ! ($foreground) ) {
416             openlog( $0, "cons,pid", "daemon" );
417             syslog( "warning", $msg );
418             closelog();
419         }
420         else {
421             print( STDERR " $msg " );
422         }
423         exit( -1 );
424     }
427 #===  FUNCTION  ================================================================
428 #         NAME:  import_modules
429 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
430 #                are stored
431 #      RETURNS:  nothing
432 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
433 #                state is on is imported by "require 'file';"
434 #===============================================================================
435 sub import_modules {
436     daemon_log(" ", 1);
438     if (not -e $modules_path) {
439         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
440     }
442     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
443     while (defined (my $file = readdir (DIR))) {
444         if (not $file =~ /(\S*?).pm$/) {
445             next;
446         }
447                 my $mod_name = $1;
449         if( $file =~ /ArpHandler.pm/ ) {
450             if( $no_arp > 0 ) {
451                 next;
452             }
453         }
454         
455         eval { require $file; };
456         if ($@) {
457             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
458             daemon_log("$@", 5);
459                 } else {
460                         my $info = eval($mod_name.'::get_module_info()');
461                         # Only load module if get_module_info() returns a non-null object
462                         if( $info ) {
463                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
464                                 $known_modules->{$mod_name} = $info;
465                                 daemon_log("0 INFO: module $mod_name loaded", 5);
466                         }
467                 }
468     }   
469     close (DIR);
473 #===  FUNCTION  ================================================================
474 #         NAME:  sig_int_handler
475 #   PARAMETERS:  signal - string - signal arose from system
476 #      RETURNS:  noting
477 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
478 #===============================================================================
479 sub sig_int_handler {
480     my ($signal) = @_;
482 #       if (defined($ldap_handle)) {
483 #               $ldap_handle->disconnect;
484 #       }
485     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
486     
488     daemon_log("shutting down gosa-si-server", 1);
489     system("kill `ps -C gosa-si-server -o pid=`");
491 $SIG{INT} = \&sig_int_handler;
494 sub check_key_and_xml_validity {
495     my ($crypted_msg, $module_key, $session_id) = @_;
496     my $msg;
497     my $msg_hash;
498     my $error_string;
499     eval{
500         $msg = &decrypt_msg($crypted_msg, $module_key);
502         if ($msg =~ /<xml>/i){
503             $msg =~ s/\s+/ /g;  # just for better daemon_log
504             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
505             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
507             ##############
508             # check header
509             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
510             my $header_l = $msg_hash->{'header'};
511             if( 1 > @{$header_l} ) { die 'empty header tag'; }
512             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
513             my $header = @{$header_l}[0];
514             if( 0 == length $header) { die 'empty string in header tag'; }
516             ##############
517             # check source
518             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
519             my $source_l = $msg_hash->{'source'};
520             if( 1 > @{$source_l} ) { die 'empty source tag'; }
521             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
522             my $source = @{$source_l}[0];
523             if( 0 == length $source) { die 'source error'; }
525             ##############
526             # check target
527             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
528             my $target_l = $msg_hash->{'target'};
529             if( 1 > @{$target_l} ) { die 'empty target tag'; }
530         }
531     };
532     if($@) {
533         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
534         $msg = undef;
535         $msg_hash = undef;
536     }
538     return ($msg, $msg_hash);
542 sub check_outgoing_xml_validity {
543     my ($msg) = @_;
545     my $msg_hash;
546     eval{
547         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
549         ##############
550         # check header
551         my $header_l = $msg_hash->{'header'};
552         if( 1 != @{$header_l} ) {
553             die 'no or more than one headers specified';
554         }
555         my $header = @{$header_l}[0];
556         if( 0 == length $header) {
557             die 'header has length 0';
558         }
560         ##############
561         # check source
562         my $source_l = $msg_hash->{'source'};
563         if( 1 != @{$source_l} ) {
564             die 'no or more than 1 sources specified';
565         }
566         my $source = @{$source_l}[0];
567         if( 0 == length $source) {
568             die 'source has length 0';
569         }
570         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
571                 $source =~ /^GOSA$/i ) {
572             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
573         }
574         
575         ##############
576         # check target  
577         my $target_l = $msg_hash->{'target'};
578         if( 0 == @{$target_l} ) {
579             die "no targets specified";
580         }
581         foreach my $target (@$target_l) {
582             if( 0 == length $target) {
583                 die "target has length 0";
584             }
585             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
586                     $target =~ /^GOSA$/i ||
587                     $target =~ /^\*$/ ||
588                     $target =~ /KNOWN_SERVER/i ||
589                     $target =~ /JOBDB/i ||
590                     $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 ){
591                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
592             }
593         }
594     };
595     if($@) {
596         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
597         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
598         $msg_hash = undef;
599     }
601     return ($msg_hash);
605 sub input_from_known_server {
606     my ($input, $remote_ip, $session_id) = @_ ;  
607     my ($msg, $msg_hash, $module);
609     my $sql_statement= "SELECT * FROM known_server";
610     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
612     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
613         my $host_name = $hit->{hostname};
614         if( not $host_name =~ "^$remote_ip") {
615             next;
616         }
617         my $host_key = $hit->{hostkey};
618         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
619         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
621         # check if module can open msg envelope with module key
622         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
623         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
624             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
625             daemon_log("$@", 8);
626             next;
627         }
628         else {
629             $msg = $tmp_msg;
630             $msg_hash = $tmp_msg_hash;
631             $module = "ServerPackages";
632             last;
633         }
634     }
636     if( (!$msg) || (!$msg_hash) || (!$module) ) {
637         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
638     }
639   
640     return ($msg, $msg_hash, $module);
644 sub input_from_known_client {
645     my ($input, $remote_ip, $session_id) = @_ ;  
646     my ($msg, $msg_hash, $module);
648     my $sql_statement= "SELECT * FROM known_clients";
649     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
650     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
651         my $host_name = $hit->{hostname};
652         if( not $host_name =~ /^$remote_ip:\d*$/) {
653                 next;
654                 }
655         my $host_key = $hit->{hostkey};
656         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
657         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
659         # check if module can open msg envelope with module key
660         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
662         if( (!$msg) || (!$msg_hash) ) {
663             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
664             &daemon_log("$@", 8);
665             next;
666         }
667         else {
668             $module = "ClientPackages";
669             last;
670         }
671     }
673     if( (!$msg) || (!$msg_hash) || (!$module) ) {
674         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
675     }
677     return ($msg, $msg_hash, $module);
681 sub input_from_unknown_host {
682     no strict "refs";
683     my ($input, $session_id) = @_ ;
684     my ($msg, $msg_hash, $module);
685     my $error_string;
686     
687         my %act_modules = %$known_modules;
688         
689     while( my ($mod, $info) = each(%act_modules)) {
691         # check a key exists for this module
692         my $module_key = ${$mod."_key"};
693         if( not defined $module_key ) {
694             if( $mod eq 'ArpHandler' ) {
695                 next;
696             }
697             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
698             next;
699         }
700         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
702         # check if module can open msg envelope with module key
703         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
704         if( (not defined $msg) || (not defined $msg_hash) ) {
705             next;
706         }
707         else {
708             $module = $mod;
709             last;
710         }
711     }
713     if( (!$msg) || (!$msg_hash) || (!$module)) {
714         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
715     }
717     return ($msg, $msg_hash, $module);
721 sub create_ciphering {
722     my ($passwd) = @_;
723         if((!defined($passwd)) || length($passwd)==0) {
724                 $passwd = "";
725         }
726     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
727     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
728     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
729     $my_cipher->set_iv($iv);
730     return $my_cipher;
734 sub encrypt_msg {
735     my ($msg, $key) = @_;
736     my $my_cipher = &create_ciphering($key);
737     my $len;
738     {
739             use bytes;
740             $len= 16-length($msg)%16;
741     }
742     $msg = "\0"x($len).$msg;
743     $msg = $my_cipher->encrypt($msg);
744     chomp($msg = &encode_base64($msg));
745     # there are no newlines allowed inside msg
746     $msg=~ s/\n//g;
747     return $msg;
751 sub decrypt_msg {
753     my ($msg, $key) = @_ ;
754     $msg = &decode_base64($msg);
755     my $my_cipher = &create_ciphering($key);
756     $msg = $my_cipher->decrypt($msg); 
757     $msg =~ s/\0*//g;
758     return $msg;
762 sub get_encrypt_key {
763     my ($target) = @_ ;
764     my $encrypt_key;
765     my $error = 0;
767     # target can be in known_server
768     if( not defined $encrypt_key ) {
769         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
770         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
771         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
772             my $host_name = $hit->{hostname};
773             if( $host_name ne $target ) {
774                 next;
775             }
776             $encrypt_key = $hit->{hostkey};
777             last;
778         }
779     }
781     # target can be in known_client
782     if( not defined $encrypt_key ) {
783         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
784         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
785         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
786             my $host_name = $hit->{hostname};
787             if( $host_name ne $target ) {
788                 next;
789             }
790             $encrypt_key = $hit->{hostkey};
791             last;
792         }
793     }
795     return $encrypt_key;
799 #===  FUNCTION  ================================================================
800 #         NAME:  open_socket
801 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
802 #                [PeerPort] string necessary if port not appended by PeerAddr
803 #      RETURNS:  socket IO::Socket::INET
804 #  DESCRIPTION:  open a socket to PeerAddr
805 #===============================================================================
806 sub open_socket {
807     my ($PeerAddr, $PeerPort) = @_ ;
808     if(defined($PeerPort)){
809         $PeerAddr = $PeerAddr.":".$PeerPort;
810     }
811     my $socket;
812     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
813             Porto => "tcp",
814             Type => SOCK_STREAM,
815             Timeout => 5,
816             );
817     if(not defined $socket) {
818         return;
819     }
820 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
821     return $socket;
825 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
826 #===  FUNCTION  ================================================================
827 #         NAME:  get_ip 
828 #   PARAMETERS:  interface name (i.e. eth0)
829 #      RETURNS:  (ip address) 
830 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
831 #===============================================================================
832 #sub get_ip {
833 #       my $ifreq= shift;
834 #       my $result= "";
835 #       my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
836 #       my $proto= getprotobyname('ip');
838 #       socket SOCKET, PF_INET, SOCK_DGRAM, $proto
839 #               or die "socket: $!";
841 #       if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
842 #               my ($if, $sin)    = unpack 'a16 a16', $ifreq;
843 #               my ($port, $addr) = sockaddr_in $sin;
844 #               my $ip            = inet_ntoa $addr;
846 #               if ($ip && length($ip) > 0) {
847 #                       $result = $ip;
848 #               }
849 #       }
851 #       return $result;
852 #}
855 sub get_local_ip_for_remote_ip {
856         my $remote_ip= shift;
857         my $result="0.0.0.0";
859         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
860                 if($remote_ip eq "127.0.0.1") {
861                         $result = "127.0.0.1";
862                 } else {
863                         my $PROC_NET_ROUTE= ('/proc/net/route');
865                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
866                                 or die "Could not open $PROC_NET_ROUTE";
868                         my @ifs = <PROC_NET_ROUTE>;
870                         close(PROC_NET_ROUTE);
872                         # Eat header line
873                         shift @ifs;
874                         chomp @ifs;
875                         foreach my $line(@ifs) {
876                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
877                                 my $destination;
878                                 my $mask;
879                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
880                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
881                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
882                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
883                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
884                                         # destination matches route, save mac and exit
885                                         $result= &get_ip($Iface);
886                                         last;
887                                 }
888                         }
889                 }
890         } else {
891                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
892         }
893         return $result;
897 sub send_msg_to_target {
898     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
899     my $error = 0;
900     my $header;
901     my $timestamp = &get_time();
902     my $new_status;
903     my $act_status;
904     my ($sql_statement, $res);
905   
906     if( $msg_header ) {
907         $header = "'$msg_header'-";
908     } else {
909         $header = "";
910     }
912         # Patch the source ip
913         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
914                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
915                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
916         }
918     # encrypt xml msg
919     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
921     # opensocket
922     my $socket = &open_socket($address);
923     if( !$socket ) {
924         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
925         $error++;
926     }
927     
928     if( $error == 0 ) {
929         # send xml msg
930         print $socket $crypted_msg."\n";
932         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
933         daemon_log("$session_id DEBUG: message:\n$msg", 9);
934         
935     }
937     # close socket in any case
938     if( $socket ) {
939         close $socket;
940     }
942     if( $error > 0 ) { $new_status = "down"; }
943     else { $new_status = $msg_header; }
946     # known_clients
947     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
948     $res = $known_clients_db->select_dbentry($sql_statement);
949     if( keys(%$res) == 1) {
950         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
951         if ($act_status eq "down" && $new_status eq "down") {
952             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
953             $res = $known_clients_db->del_dbentry($sql_statement);
954             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
955         } else { 
956             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
957             $res = $known_clients_db->update_dbentry($sql_statement);
958             if($new_status eq "down"){
959                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
960             } else {
961                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
962             }
963         }
964     }
966     # known_server
967     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
968     $res = $known_server_db->select_dbentry($sql_statement);
969     if( keys(%$res) == 1) {
970         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
971         if ($act_status eq "down" && $new_status eq "down") {
972             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
973             $res = $known_server_db->del_dbentry($sql_statement);
974             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
975         } 
976         else { 
977             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
978             $res = $known_server_db->update_dbentry($sql_statement);
979             if($new_status eq "down"){
980                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
981             } else {
982                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
983             }
984         }
985     }
986     return $error; 
990 sub update_jobdb_status_for_send_msgs {
991     my ($answer, $error) = @_;
992     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
993         my $jobdb_id = $1;
994             
995         # sending msg faild
996         if( $error ) {
997             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
998                 my $sql_statement = "UPDATE $job_queue_tn ".
999                     "SET status='error', result='can not deliver msg, please consult log file' ".
1000                     "WHERE id=$jobdb_id";
1001                 my $res = $job_db->update_dbentry($sql_statement);
1002             }
1004         # sending msg was successful
1005         } else {
1006             my $sql_statement = "UPDATE $job_queue_tn ".
1007                 "SET status='done' ".
1008                 "WHERE id=$jobdb_id AND status='processed'";
1009             my $res = $job_db->update_dbentry($sql_statement);
1010         }
1011     }
1015 sub sig_handler {
1016         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1017         daemon_log("0 INFO got signal '$signal'", 1); 
1018         $kernel->sig_handled();
1019         return;
1023 sub msg_to_decrypt {
1024     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1025     my $session_id = $session->ID;
1026     my ($msg, $msg_hash, $module);
1027     my $error = 0;
1029     # hole neue msg aus @msgs_to_decrypt
1030     my $next_msg = shift @msgs_to_decrypt;
1031     
1032     # entschlüssle sie
1034     # msg is from a new client or gosa
1035     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1036     # msg is from a gosa-si-server or gosa-si-bus
1037     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1039     }
1040     # msg is from a gosa-si-client
1041     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1043     }
1044     # an error occurred
1045     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1046         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1047         # could not understand a msg from its server the client cause a re-registering process
1048         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1049                 "' to cause a re-registering of the client if necessary", 5);
1050         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1051         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1052         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1053             my $host_name = $hit->{'hostname'};
1054             my $host_key = $hit->{'hostkey'};
1055             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1056             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1057             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1058         }
1059         $error++;
1060     }
1063     my $header;
1064     my $target;
1065     my $source;
1066     my $done = 0;
1067     my $sql;
1068     my $res;
1069     # check whether this message should be processed here
1070     if ($error == 0) {
1071         $header = @{$msg_hash->{'header'}}[0];
1072         $target = @{$msg_hash->{'target'}}[0];
1073         $source = @{$msg_hash->{'source'}}[0];
1074         my ($target_ip, $target_port) = split(':', $target);
1075                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1076                         my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1077                 }
1079         # target and source is equal to GOSA -> process here
1080         if (not $done) {
1081             if ($target eq "GOSA" && $source eq "GOSA") {
1082                 $done = 1;                    
1083             }
1084         }
1086         # target is own address without forward_to_gosa-tag -> process here
1087         if (not $done) {
1088             if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1089                 $done = 1;
1090                 if ($source eq "GOSA") {
1091                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1092                 }
1093                 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1094             }
1095         }
1097         # target is a client address in known_clients -> process here
1098         if (not $done) {
1099             $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1100             $res = $known_clients_db->select_dbentry($sql);
1101             if (keys(%$res) > 0) {
1102                 $done = 1; 
1103                 my $hostname = $res->{1}->{'hostname'};
1104                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1105                 print STDERR "target is a client address in known_clients -> process here\n";
1106             }
1107         }
1109         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1110         if (not $done) {
1111             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1112             my $gosa_at;
1113             my $gosa_session_id;
1114             if (($target eq $server_address) && (defined $forward_to_gosa)){
1115                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1116                 if ($gosa_at ne $server_address) {
1117                     $done = 1;
1118                     print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1119                 }
1120             }
1121         }
1123         # if message should be processed here -> add message to incoming_db
1124         if ($done) {
1126             # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1127             # so gosa-si-server knows how to process this kind of messages
1128             if ($header =~ /^gosa_/ || $header =~ /job_/) {
1129                 $module = "GosaPackages";
1130             }
1132             my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1133                     primkey=>[],
1134                     headertag=>$header,
1135                     targettag=>$target,
1136                     xmlmessage=>&encode_base64($msg),
1137                     timestamp=>&get_time,
1138                     module=>$module,
1139                     sessionid=>$session_id,
1140                     } );
1142         }
1144         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1145         if (not $done) {
1146             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1147             my $gosa_at;
1148             my $gosa_session_id;
1149             if (($target eq $server_address) && (defined $forward_to_gosa)){
1150                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1151                 if ($gosa_at eq $server_address) {
1152                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1153                     if( defined $session_reference ) {
1154                         $heap = $session_reference->get_heap();
1155                     }
1156                     if(exists $heap->{'client'}) {
1157                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1158                         $heap->{'client'}->put($msg);
1159                     }
1160                     $done = 1;
1161                     print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1162                 }
1163             }
1165         }
1167         # target is a client address in foreign_clients -> forward to registration server
1168         if (not $done) {
1169             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1170             $res = $foreign_clients_db->select_dbentry($sql);
1171             if (keys(%$res) > 0) {
1172                 my $hostname = $res->{1}->{'hostname'};
1173                 my $regserver = $res->{1}->{'regserver'};
1174                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1175                 my $res = $known_server_db->select_dbentry($sql);
1176                 if (keys(%$res) > 0) {
1177                     my $regserver_key = $res->{1}->{'hostkey'};
1178                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1179                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1180                     if ($source eq "GOSA") {
1181                         $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1182                     }
1183                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1184                 }
1185                 $done = 1;
1186                 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1187             }
1188         }
1190         # target is a server address -> forward to server
1191         if (not $done) {
1192             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1193             $res = $known_server_db->select_dbentry($sql);
1194             if (keys(%$res) > 0) {
1195                 my $hostkey = $res->{1}->{'hostkey'};
1197                 if ($source eq "GOSA") {
1198                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1199                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1201                 }
1203                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1204                 $done = 1;
1205                 print STDERR "target is a server address -> forward to server\n";
1206             }
1209         }
1211         if (not $done) {
1212             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1213         }
1214     }
1216     return;
1220 sub next_task {
1221     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1222     my $running_task = POE::Wheel::Run->new(
1223             Program => sub { process_task($session, $heap, $task) },
1224             StdioFilter => POE::Filter::Reference->new(),
1225             StdoutEvent  => "task_result",
1226             StderrEvent  => "task_debug",
1227             CloseEvent   => "task_done",
1228             );
1229     $heap->{task}->{ $running_task->ID } = $running_task;
1232 sub handle_task_result {
1233     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1234     my $client_answer = $result->{'answer'};
1235     if( $client_answer =~ s/session_id=(\d+)$// ) {
1236         my $session_id = $1;
1237         if( defined $session_id ) {
1238             my $session_reference = $kernel->ID_id_to_session($session_id);
1239             if( defined $session_reference ) {
1240                 $heap = $session_reference->get_heap();
1241             }
1242         }
1244         if(exists $heap->{'client'}) {
1245             $heap->{'client'}->put($client_answer);
1246         }
1247     }
1248     $kernel->sig(CHLD => "child_reap");
1251 sub handle_task_debug {
1252     my $result = $_[ARG0];
1253     print STDERR "$result\n";
1256 sub handle_task_done {
1257     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1258     delete $heap->{task}->{$task_id};
1261 sub process_task {
1262     no strict "refs";
1263     my ($session, $heap, $task) = @_;
1264     my $error = 0;
1265     my $answer_l;
1266     my ($answer_header, @answer_target_l, $answer_source);
1267     my $client_answer = "";
1269     # prepare all variables needed to process message
1270     my $msg = &decode_base64($task->{'xmlmessage'});
1271     my $incoming_id = $task->{'id'};
1272     my $module = $task->{'module'};
1273     my $header =  $task->{'headertag'};
1274     my $session_id = $task->{'sessionid'};
1275     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1276     my $source = @{$msg_hash->{'source'}}[0];
1277     
1278     # set timestamp of incoming client uptodate, so client will not 
1279     # be deleted from known_clients because of expiration
1280     my $act_time = &get_time();
1281     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1282     my $res = $known_clients_db->exec_statement($sql);
1284     ######################
1285     # process incoming msg
1286     if( $error == 0) {
1287         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1288         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1289         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1291         if ( 0 < @{$answer_l} ) {
1292             my $answer_str = join("\n", @{$answer_l});
1293             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1294                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1295             }
1296             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1297         } else {
1298             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1299         }
1301     }
1302     if( !$answer_l ) { $error++ };
1304     ########
1305     # answer
1306     if( $error == 0 ) {
1308         foreach my $answer ( @{$answer_l} ) {
1309             # check outgoing msg to xml validity
1310             my $answer_hash = &check_outgoing_xml_validity($answer);
1311             if( not defined $answer_hash ) { next; }
1312             
1313             $answer_header = @{$answer_hash->{'header'}}[0];
1314             @answer_target_l = @{$answer_hash->{'target'}};
1315             $answer_source = @{$answer_hash->{'source'}}[0];
1317             # deliver msg to all targets 
1318             foreach my $answer_target ( @answer_target_l ) {
1320                 # targets of msg are all gosa-si-clients in known_clients_db
1321                 if( $answer_target eq "*" ) {
1322                     # answer is for all clients
1323                     my $sql_statement= "SELECT * FROM known_clients";
1324                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1325                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1326                         my $host_name = $hit->{hostname};
1327                         my $host_key = $hit->{hostkey};
1328                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1329                         &update_jobdb_status_for_send_msgs($answer, $error);
1330                     }
1331                 }
1333                 # targets of msg are all gosa-si-server in known_server_db
1334                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1335                     # answer is for all server in known_server
1336                     my $sql_statement= "SELECT * FROM $known_server_tn";
1337                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1338                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1339                         my $host_name = $hit->{hostname};
1340                         my $host_key = $hit->{hostkey};
1341                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1342                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1343                         &update_jobdb_status_for_send_msgs($answer, $error);
1344                     }
1345                 }
1347                 # target of msg is GOsa
1348                                 elsif( $answer_target eq "GOSA" ) {
1349                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1350                                         my $add_on = "";
1351                     if( defined $session_id ) {
1352                         $add_on = ".session_id=$session_id";
1353                     }
1354                     # answer is for GOSA and has to returned to connected client
1355                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1356                     $client_answer = $gosa_answer.$add_on;
1357                 }
1359                 # target of msg is job queue at this host
1360                 elsif( $answer_target eq "JOBDB") {
1361                     $answer =~ /<header>(\S+)<\/header>/;   
1362                     my $header;
1363                     if( defined $1 ) { $header = $1; }
1364                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1365                     &update_jobdb_status_for_send_msgs($answer, $error);
1366                 }
1368                 # target of msg is a mac address
1369                 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 ) {
1370                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1371                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1372                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1373                     my $found_ip_flag = 0;
1374                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1375                         my $host_name = $hit->{hostname};
1376                         my $host_key = $hit->{hostkey};
1377                         $answer =~ s/$answer_target/$host_name/g;
1378                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1379                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1380                         &update_jobdb_status_for_send_msgs($answer, $error);
1381                         $found_ip_flag++ ;
1382                     }   
1383                     if( $found_ip_flag == 0) {
1384                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1385                         if( $bus_activ eq "true" ) { 
1386                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1387                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1388                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1389                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1390                                 my $bus_address = $hit->{hostname};
1391                                 my $bus_key = $hit->{hostkey};
1392                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1393                                 &update_jobdb_status_for_send_msgs($answer, $error);
1394                                 last;
1395                             }
1396                         }
1398                     }
1400                 #  answer is for one specific host   
1401                 } else {
1402                     # get encrypt_key
1403                     my $encrypt_key = &get_encrypt_key($answer_target);
1404                     if( not defined $encrypt_key ) {
1405                         # unknown target, forward msg to bus
1406                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1407                         if( $bus_activ eq "true" ) { 
1408                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1409                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1410                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1411                             my $res_length = keys( %{$query_res} );
1412                             if( $res_length == 0 ){
1413                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1414                                         "no bus found in known_server", 3);
1415                             }
1416                             else {
1417                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1418                                     my $bus_key = $hit->{hostkey};
1419                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1420                                     &update_jobdb_status_for_send_msgs($answer, $error);
1421                                 }
1422                             }
1423                         }
1424                         next;
1425                     }
1426                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1427                     &update_jobdb_status_for_send_msgs($answer, $error);
1428                 }
1429             }
1430         }
1431     }
1433     my $filter = POE::Filter::Reference->new();
1434     my %result = ( 
1435             status => "seems ok to me",
1436             answer => $client_answer,
1437             );
1439     my $output = $filter->put( [ \%result ] );
1440     print @$output;
1445 sub session_start {
1446     my ($kernel) = $_[KERNEL];
1447     &trigger_db_loop($kernel);
1448     $global_kernel = $kernel;
1449     $kernel->yield('register_at_foreign_servers');
1450         $kernel->yield('create_fai_server_db', $fai_server_tn );
1451         $kernel->yield('create_fai_release_db', $fai_release_tn );
1452     $kernel->yield('watch_for_next_tasks');
1453         $kernel->sig(USR1 => "sig_handler");
1454         $kernel->sig(USR2 => "create_packages_list_db");
1455         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1456         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1457         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1458     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1459         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1460     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1464 sub trigger_db_loop {
1465         my ($kernel) = @_ ;
1466 #       $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1467 #       $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1468 #       $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1469 #    $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1470 #       $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1471 #    $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1475 sub watch_for_done_jobs {
1476     my ($kernel,$heap) = @_[KERNEL, HEAP];
1478     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1479         my $res = $job_db->select_dbentry( $sql_statement );
1481     while( my ($id, $hit) = each %{$res} ) {
1482         my $jobdb_id = $hit->{id};
1483         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1484         my $res = $job_db->del_dbentry($sql_statement); 
1485     }
1487     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1491 sub watch_for_new_jobs {
1492         if($watch_for_new_jobs_in_progress == 0) {
1493                 $watch_for_new_jobs_in_progress = 1;
1494                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1496                 # check gosa job queue for jobs with executable timestamp
1497                 my $timestamp = &get_time();
1498                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1499                 my $res = $job_db->exec_statement( $sql_statement );
1501                 # Merge all new jobs that would do the same actions
1502                 my @drops;
1503                 my $hits;
1504                 foreach my $hit (reverse @{$res} ) {
1505                         my $macaddress= lc @{$hit}[8];
1506                         my $headertag= @{$hit}[5];
1507                         if(
1508                                 defined($hits->{$macaddress}) &&
1509                                 defined($hits->{$macaddress}->{$headertag}) &&
1510                                 defined($hits->{$macaddress}->{$headertag}[0])
1511                         ) {
1512                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1513                         }
1514                         $hits->{$macaddress}->{$headertag}= $hit;
1515                 }
1517                 # Delete new jobs with a matching job in state 'processing'
1518                 foreach my $macaddress (keys %{$hits}) {
1519                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1520                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1521                                 if(defined($jobdb_id)) {
1522                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1523                                         my $res = $job_db->exec_statement( $sql_statement );
1524                                         foreach my $hit (@{$res}) {
1525                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1526                                         }
1527                                 } else {
1528                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1529                                 }
1530                         }
1531                 }
1533                 # Commit deletion
1534                 $job_db->exec_statementlist(\@drops);
1536                 # Look for new jobs that could be executed
1537                 foreach my $macaddress (keys %{$hits}) {
1539                         # Look if there is an executing job
1540                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1541                         my $res = $job_db->exec_statement( $sql_statement );
1543                         # Skip new jobs for host if there is a processing job
1544                         if(defined($res) and defined @{$res}[0]) {
1545                                 next;
1546                         }
1548                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1549                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1550                                 if(defined($jobdb_id)) {
1551                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1553                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1554                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1555                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1557                                         # expect macaddress is unique!!!!!!
1558                                         my $target = $res_hash->{1}->{hostname};
1560                                         # change header
1561                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1563                                         # add sqlite_id
1564                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1566                                         $job_msg =~ /<header>(\S+)<\/header>/;
1567                                         my $header = $1 ;
1568                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1570                                         # update status in job queue to 'processing'
1571                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1572                                         my $res = $job_db->update_dbentry($sql_statement);
1573 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1575                                         # We don't want parallel processing
1576                                         last;
1577                                 }
1578                         }
1579                 }
1581                 $watch_for_new_jobs_in_progress = 0;
1582                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1583         }
1587 sub watch_for_new_messages {
1588     my ($kernel,$heap) = @_[KERNEL, HEAP];
1589     my @coll_user_msg;   # collection list of outgoing messages
1590     
1591     # check messaging_db for new incoming messages with executable timestamp
1592     my $timestamp = &get_time();
1593     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1594     my $res = $messaging_db->exec_statement( $sql_statement );
1595         foreach my $hit (@{$res}) {
1597         # create outgoing messages
1598         my $message_to = @{$hit}[3];
1599         # translate message_to to plain login name
1600         my @message_to_l = split(/,/, $message_to);  
1601                 my %receiver_h; 
1602                 foreach my $receiver (@message_to_l) {
1603                         if ($receiver =~ /^u_([\s\S]*)$/) {
1604                                 $receiver_h{$1} = 0;
1605                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1606                                 my $group_name = $1;
1607                                 # fetch all group members from ldap and add them to receiver hash
1608                                 my $ldap_handle = &get_ldap_handle();
1609                                 if (defined $ldap_handle) {
1610                                                 my $mesg = $ldap_handle->search(
1611                                                                                 base => $ldap_base,
1612                                                                                 scope => 'sub',
1613                                                                                 attrs => ['memberUid'],
1614                                                                                 filter => "cn=$group_name",
1615                                                                                 );
1616                                                 if ($mesg->count) {
1617                                                                 my @entries = $mesg->entries;
1618                                                                 foreach my $entry (@entries) {
1619                                                                                 my @receivers= $entry->get_value("memberUid");
1620                                                                                 foreach my $receiver (@receivers) { 
1621                                                                                                 $receiver_h{$1} = 0;
1622                                                                                 }
1623                                                                 }
1624                                                 } 
1625                                                 # translating errors ?
1626                                                 if ($mesg->code) {
1627                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1628                                                 }
1629                                 # ldap handle error ?           
1630                                 } else {
1631                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1632                                 }
1633                         } else {
1634                                 my $sbjct = &encode_base64(@{$hit}[1]);
1635                                 my $msg = &encode_base64(@{$hit}[7]);
1636                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1637                         }
1638                 }
1639                 my @receiver_l = keys(%receiver_h);
1641         my $message_id = @{$hit}[0];
1643         #add each outgoing msg to messaging_db
1644         my $receiver;
1645         foreach $receiver (@receiver_l) {
1646             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1647                 "VALUES ('".
1648                 $message_id."', '".    # id
1649                 @{$hit}[1]."', '".     # subject
1650                 @{$hit}[2]."', '".     # message_from
1651                 $receiver."', '".      # message_to
1652                 "none"."', '".         # flag
1653                 "out"."', '".          # direction
1654                 @{$hit}[6]."', '".     # delivery_time
1655                 @{$hit}[7]."', '".     # message
1656                 $timestamp."'".     # timestamp
1657                 ")";
1658             &daemon_log("M DEBUG: $sql_statement", 1);
1659             my $res = $messaging_db->exec_statement($sql_statement);
1660             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1661         }
1663         # set incoming message to flag d=deliverd
1664         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1665         &daemon_log("M DEBUG: $sql_statement", 7);
1666         $res = $messaging_db->update_dbentry($sql_statement);
1667         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1668     }
1670     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1671     return;
1674 sub watch_for_delivery_messages {
1675     my ($kernel, $heap) = @_[KERNEL, HEAP];
1677     # select outgoing messages
1678     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1679     #&daemon_log("0 DEBUG: $sql", 7);
1680     my $res = $messaging_db->exec_statement( $sql_statement );
1681     
1682     # build out msg for each    usr
1683     foreach my $hit (@{$res}) {
1684         my $receiver = @{$hit}[3];
1685         my $msg_id = @{$hit}[0];
1686         my $subject = @{$hit}[1];
1687         my $message = @{$hit}[7];
1689         # resolve usr -> host where usr is logged in
1690         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1691         #&daemon_log("0 DEBUG: $sql", 7);
1692         my $res = $login_users_db->exec_statement($sql);
1694         # reciver is logged in nowhere
1695         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1697                 my $send_succeed = 0;
1698                 foreach my $hit (@$res) {
1699                                 my $receiver_host = @$hit[0];
1700                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1702                                 # fetch key to encrypt msg propperly for usr/host
1703                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1704                                 &daemon_log("0 DEBUG: $sql", 7);
1705                                 my $res = $known_clients_db->select_dbentry($sql);
1707                                 # host is already down
1708                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1710                                 # host is on
1711                                 my $receiver_key = @{@{$res}[0]}[2];
1712                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1713                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1714                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1715                                 if ($error == 0 ) {
1716                                         $send_succeed++ ;
1717                                 }
1718                 }
1720                 if ($send_succeed) {
1721                                 # set outgoing msg at db to deliverd
1722                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1723                                 &daemon_log("0 DEBUG: $sql", 7);
1724                                 my $res = $messaging_db->exec_statement($sql); 
1725                 }
1726         }
1728     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1729     return;
1733 sub watch_for_done_messages {
1734     my ($kernel,$heap) = @_[KERNEL, HEAP];
1736     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1737     #&daemon_log("0 DEBUG: $sql", 7);
1738     my $res = $messaging_db->exec_statement($sql); 
1740     foreach my $hit (@{$res}) {
1741         my $msg_id = @{$hit}[0];
1743         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1744         #&daemon_log("0 DEBUG: $sql", 7); 
1745         my $res = $messaging_db->exec_statement($sql);
1747         # not all usr msgs have been seen till now
1748         if ( ref(@$res[0]) eq "ARRAY") { next; }
1749         
1750         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1751         #&daemon_log("0 DEBUG: $sql", 7);
1752         $res = $messaging_db->exec_statement($sql);
1753     
1754     }
1756     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1757     return;
1761 sub watch_for_old_known_clients {
1762     my ($kernel,$heap) = @_[KERNEL, HEAP];
1764     my $sql_statement = "SELECT * FROM $known_clients_tn";
1765     my $res = $known_clients_db->select_dbentry( $sql_statement );
1767     my $act_time = int(&get_time());
1769     while ( my ($hit_num, $hit) = each %$res) {
1770         my $expired_timestamp = int($hit->{'timestamp'});
1771         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1772         my $dt = DateTime->new( year   => $1,
1773                 month  => $2,
1774                 day    => $3,
1775                 hour   => $4,
1776                 minute => $5,
1777                 second => $6,
1778                 );
1780         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1781         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1782         if ($act_time > $expired_timestamp) {
1783             my $hostname = $hit->{'hostname'};
1784             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1785             my $del_res = $known_clients_db->exec_statement($del_sql);
1787             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1788         }
1790     }
1792     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1796 sub watch_for_next_tasks {
1797     my ($kernel,$heap) = @_[KERNEL, HEAP];
1799     my $sql = "SELECT * FROM $incoming_tn";
1800     my $res = $incoming_db->select_dbentry($sql);
1802     while ( my ($hit_num, $hit) = each %$res) {
1803         my $headertag = $hit->{'headertag'};
1804         if ($headertag =~ /^answer_(\d+)/) {
1805             # do not start processing, this message is for a still running POE::Wheel
1806             next;
1807         }
1808         my $message_id = $hit->{'id'};
1809         $kernel->yield('next_task', $hit);
1811         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1812         my $res = $incoming_db->exec_statement($sql);
1813     }
1815     $kernel->delay_set('watch_for_next_tasks', 1); 
1819 sub get_ldap_handle {
1820         my ($session_id) = @_;
1821         my $heap;
1822         my $ldap_handle;
1824         if (not defined $session_id ) { $session_id = 0 };
1825         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1827         if ($session_id == 0) {
1828                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1829                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1830                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1832         } else {
1833                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1834                 if( defined $session_reference ) {
1835                         $heap = $session_reference->get_heap();
1836                 }
1838                 if (not defined $heap) {
1839                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1840                         return;
1841                 }
1843                 # TODO: This "if" is nonsense, because it doesn't prove that the
1844                 #       used handle is still valid - or if we've to reconnect...
1845                 #if (not exists $heap->{ldap_handle}) {
1846                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1847                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1848                         $heap->{ldap_handle} = $ldap_handle;
1849                 #}
1850         }
1851         return $ldap_handle;
1855 sub change_fai_state {
1856     my ($st, $targets, $session_id) = @_;
1857     $session_id = 0 if not defined $session_id;
1858     # Set FAI state to localboot
1859     my %mapActions= (
1860         reboot    => '',
1861         update    => 'softupdate',
1862         localboot => 'localboot',
1863         reinstall => 'install',
1864         rescan    => '',
1865         wake      => '',
1866         memcheck  => 'memcheck',
1867         sysinfo   => 'sysinfo',
1868         install   => 'install',
1869     );
1871     # Return if this is unknown
1872     if (!exists $mapActions{ $st }){
1873         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1874       return;
1875     }
1877     my $state= $mapActions{ $st };
1879     my $ldap_handle = &get_ldap_handle($session_id);
1880     if( defined($ldap_handle) ) {
1882       # Build search filter for hosts
1883         my $search= "(&(objectClass=GOhard)";
1884         foreach (@{$targets}){
1885             $search.= "(macAddress=$_)";
1886         }
1887         $search.= ")";
1889       # If there's any host inside of the search string, procress them
1890         if (!($search =~ /macAddress/)){
1891             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1892             return;
1893         }
1895       # Perform search for Unit Tag
1896       my $mesg = $ldap_handle->search(
1897           base   => $ldap_base,
1898           scope  => 'sub',
1899           attrs  => ['dn', 'FAIstate', 'objectClass'],
1900           filter => "$search"
1901           );
1903           if ($mesg->count) {
1904                   my @entries = $mesg->entries;
1905                   foreach my $entry (@entries) {
1906                           # Only modify entry if it is not set to '$state'
1907                           if ($entry->get_value("FAIstate") ne "$state"){
1908                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1909                                   my $result;
1910                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1911                                   if (exists $tmp{'FAIobject'}){
1912                                           if ($state eq ''){
1913                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1914                                                           delete => [ FAIstate => [] ] ]);
1915                                           } else {
1916                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1917                                                           replace => [ FAIstate => $state ] ]);
1918                                           }
1919                                   } elsif ($state ne ''){
1920                                           $result= $ldap_handle->modify($entry->dn, changes => [
1921                                                   add     => [ objectClass => 'FAIobject' ],
1922                                                   add     => [ FAIstate => $state ] ]);
1923                                   }
1925                                   # Errors?
1926                                   if ($result->code){
1927                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1928                                   }
1929                           } else {
1930                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1931                           }  
1932                   }
1933           }
1934     # if no ldap handle defined
1935     } else {
1936         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1937     }
1942 sub change_goto_state {
1943     my ($st, $targets, $session_id) = @_;
1944     $session_id = 0  if not defined $session_id;
1946     # Switch on or off?
1947     my $state= $st eq 'active' ? 'active': 'locked';
1949     my $ldap_handle = &get_ldap_handle($session_id);
1950     if( defined($ldap_handle) ) {
1952       # Build search filter for hosts
1953       my $search= "(&(objectClass=GOhard)";
1954       foreach (@{$targets}){
1955         $search.= "(macAddress=$_)";
1956       }
1957       $search.= ")";
1959       # If there's any host inside of the search string, procress them
1960       if (!($search =~ /macAddress/)){
1961         return;
1962       }
1964       # Perform search for Unit Tag
1965       my $mesg = $ldap_handle->search(
1966           base   => $ldap_base,
1967           scope  => 'sub',
1968           attrs  => ['dn', 'gotoMode'],
1969           filter => "$search"
1970           );
1972       if ($mesg->count) {
1973         my @entries = $mesg->entries;
1974         foreach my $entry (@entries) {
1976           # Only modify entry if it is not set to '$state'
1977           if ($entry->get_value("gotoMode") ne $state){
1979             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1980             my $result;
1981             $result= $ldap_handle->modify($entry->dn, changes => [
1982                                                 replace => [ gotoMode => $state ] ]);
1984             # Errors?
1985             if ($result->code){
1986               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1987             }
1989           }
1990         }
1991       }
1993     }
1997 sub run_create_fai_server_db {
1998     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1999     my $session_id = $session->ID;
2000     my $task = POE::Wheel::Run->new(
2001             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2002             StdoutEvent  => "session_run_result",
2003             StderrEvent  => "session_run_debug",
2004             CloseEvent   => "session_run_done",
2005             );
2007     $heap->{task}->{ $task->ID } = $task;
2008     return;
2012 sub create_fai_server_db {
2013     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2014         my $result;
2016         if (not defined $session_id) { $session_id = 0; }
2017     my $ldap_handle = &get_ldap_handle();
2018         if(defined($ldap_handle)) {
2019                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2020                 my $mesg= $ldap_handle->search(
2021                         base   => $ldap_base,
2022                         scope  => 'sub',
2023                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2024                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2025                 );
2026                 if($mesg->{'resultCode'} == 0 &&
2027                    $mesg->count != 0) {
2028                    foreach my $entry (@{$mesg->{entries}}) {
2029                            if($entry->exists('FAIrepository')) {
2030                                    # Add an entry for each Repository configured for server
2031                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2032                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2033                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2034                                                    $result= $fai_server_db->add_dbentry( { 
2035                                                                    table => $table_name,
2036                                                                    primkey => ['server', 'release', 'tag'],
2037                                                                    server => $tmp_url,
2038                                                                    release => $tmp_release,
2039                                                                    sections => $tmp_sections,
2040                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2041                                                            } );
2042                                            }
2043                                    }
2044                            }
2045                    }
2046                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2048                 # TODO: Find a way to post the 'create_packages_list_db' event
2049                 if(not defined($dont_create_packages_list)) {
2050                         &create_packages_list_db(undef, undef, $session_id);
2051                 }
2052         }       
2053     
2054     $ldap_handle->disconnect;
2055         return $result;
2059 sub run_create_fai_release_db {
2060     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2061         my $session_id = $session->ID;
2062     my $task = POE::Wheel::Run->new(
2063             Program => sub { &create_fai_release_db($table_name, $session_id) },
2064             StdoutEvent  => "session_run_result",
2065             StderrEvent  => "session_run_debug",
2066             CloseEvent   => "session_run_done",
2067             );
2069     $heap->{task}->{ $task->ID } = $task;
2070     return;
2074 sub create_fai_release_db {
2075         my ($table_name, $session_id) = @_;
2076         my $result;
2078     # used for logging
2079     if (not defined $session_id) { $session_id = 0; }
2081     my $ldap_handle = &get_ldap_handle();
2082         if(defined($ldap_handle)) {
2083                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2084                 my $mesg= $ldap_handle->search(
2085                         base   => $ldap_base,
2086                         scope  => 'sub',
2087                         attrs  => [],
2088                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2089                 );
2090                 if($mesg->{'resultCode'} == 0 &&
2091                         $mesg->count != 0) {
2092                         # Walk through all possible FAI container ou's
2093                         my @sql_list;
2094                         my $timestamp= &get_time();
2095                         foreach my $ou (@{$mesg->{entries}}) {
2096                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2097                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2098                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2099                                         if(@tmp_array) {
2100                                                 foreach my $entry (@tmp_array) {
2101                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2102                                                                 my $sql= 
2103                                                                 "INSERT INTO $table_name "
2104                                                                 ."(timestamp, release, class, type, state) VALUES ("
2105                                                                 .$timestamp.","
2106                                                                 ."'".$entry->{'release'}."',"
2107                                                                 ."'".$entry->{'class'}."',"
2108                                                                 ."'".$entry->{'type'}."',"
2109                                                                 ."'".$entry->{'state'}."')";
2110                                                                 push @sql_list, $sql;
2111                                                         }
2112                                                 }
2113                                         }
2114                                 }
2115                         }
2117                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2118                         if(@sql_list) {
2119                                 unshift @sql_list, "VACUUM";
2120                                 unshift @sql_list, "DELETE FROM $table_name";
2121                                 $fai_release_db->exec_statementlist(\@sql_list);
2122                         }
2123                         daemon_log("$session_id DEBUG: Done with inserting",7);
2124                 }
2125                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2126         }
2127     $ldap_handle->disconnect;
2128         return $result;
2131 sub get_fai_types {
2132         my $tmp_classes = shift || return undef;
2133         my @result;
2135         foreach my $type(keys %{$tmp_classes}) {
2136                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2137                         my $entry = {
2138                                 type => $type,
2139                                 state => $tmp_classes->{$type}[0],
2140                         };
2141                         push @result, $entry;
2142                 }
2143         }
2145         return @result;
2148 sub get_fai_state {
2149         my $result = "";
2150         my $tmp_classes = shift || return $result;
2152         foreach my $type(keys %{$tmp_classes}) {
2153                 if(defined($tmp_classes->{$type}[0])) {
2154                         $result = $tmp_classes->{$type}[0];
2155                         
2156                 # State is equal for all types in class
2157                         last;
2158                 }
2159         }
2161         return $result;
2164 sub resolve_fai_classes {
2165         my ($fai_base, $ldap_handle, $session_id) = @_;
2166         if (not defined $session_id) { $session_id = 0; }
2167         my $result;
2168         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2169         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2170         my $fai_classes;
2172         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2173         my $mesg= $ldap_handle->search(
2174                 base   => $fai_base,
2175                 scope  => 'sub',
2176                 attrs  => ['cn','objectClass','FAIstate'],
2177                 filter => $fai_filter,
2178         );
2179         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2181         if($mesg->{'resultCode'} == 0 &&
2182                 $mesg->count != 0) {
2183                 foreach my $entry (@{$mesg->{entries}}) {
2184                         if($entry->exists('cn')) {
2185                                 my $tmp_dn= $entry->dn();
2187                                 # Skip classname and ou dn parts for class
2188                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2190                                 # Skip classes without releases
2191                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2192                                         next;
2193                                 }
2195                                 my $tmp_cn= $entry->get_value('cn');
2196                                 my $tmp_state= $entry->get_value('FAIstate');
2198                                 my $tmp_type;
2199                                 # Get FAI type
2200                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2201                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2202                                                 $tmp_type= $oclass;
2203                                                 last;
2204                                         }
2205                                 }
2207                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2208                                         # A Subrelease
2209                                         my @sub_releases = split(/,/, $tmp_release);
2211                                         # Walk through subreleases and build hash tree
2212                                         my $hash;
2213                                         while(my $tmp_sub_release = pop @sub_releases) {
2214                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2215                                         }
2216                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2217                                 } else {
2218                                         # A branch, no subrelease
2219                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2220                                 }
2221                         } elsif (!$entry->exists('cn')) {
2222                                 my $tmp_dn= $entry->dn();
2223                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2225                                 # Skip classes without releases
2226                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2227                                         next;
2228                                 }
2230                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2231                                         # A Subrelease
2232                                         my @sub_releases= split(/,/, $tmp_release);
2234                                         # Walk through subreleases and build hash tree
2235                                         my $hash;
2236                                         while(my $tmp_sub_release = pop @sub_releases) {
2237                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2238                                         }
2239                                         # Remove the last two characters
2240                                         chop($hash);
2241                                         chop($hash);
2243                                         eval('$fai_classes->'.$hash.'= {}');
2244                                 } else {
2245                                         # A branch, no subrelease
2246                                         if(!exists($fai_classes->{$tmp_release})) {
2247                                                 $fai_classes->{$tmp_release} = {};
2248                                         }
2249                                 }
2250                         }
2251                 }
2253                 # The hash is complete, now we can honor the copy-on-write based missing entries
2254                 foreach my $release (keys %$fai_classes) {
2255                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2256                 }
2257         }
2258         return $result;
2261 sub apply_fai_inheritance {
2262        my $fai_classes = shift || return {};
2263        my $tmp_classes;
2265        # Get the classes from the branch
2266        foreach my $class (keys %{$fai_classes}) {
2267                # Skip subreleases
2268                if($class =~ /^ou=.*$/) {
2269                        next;
2270                } else {
2271                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2272                }
2273        }
2275        # Apply to each subrelease
2276        foreach my $subrelease (keys %{$fai_classes}) {
2277                if($subrelease =~ /ou=/) {
2278                        foreach my $tmp_class (keys %{$tmp_classes}) {
2279                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2280                                        $fai_classes->{$subrelease}->{$tmp_class} =
2281                                        deep_copy($tmp_classes->{$tmp_class});
2282                                } else {
2283                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2284                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2285                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2286                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2287                                                }
2288                                        }
2289                                }
2290                        }
2291                }
2292        }
2294        # Find subreleases in deeper levels
2295        foreach my $subrelease (keys %{$fai_classes}) {
2296                if($subrelease =~ /ou=/) {
2297                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2298                                if($subsubrelease =~ /ou=/) {
2299                                        apply_fai_inheritance($fai_classes->{$subrelease});
2300                                }
2301                        }
2302                }
2303        }
2305        return $fai_classes;
2308 sub get_fai_release_entries {
2309         my $tmp_classes = shift || return;
2310         my $parent = shift || "";
2311         my @result = shift || ();
2313         foreach my $entry (keys %{$tmp_classes}) {
2314                 if(defined($entry)) {
2315                         if($entry =~ /^ou=.*$/) {
2316                                 my $release_name = $entry;
2317                                 $release_name =~ s/ou=//g;
2318                                 if(length($parent)>0) {
2319                                         $release_name = $parent."/".$release_name;
2320                                 }
2321                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2322                                 foreach my $bufentry(@bufentries) {
2323                                         push @result, $bufentry;
2324                                 }
2325                         } else {
2326                                 my @types = get_fai_types($tmp_classes->{$entry});
2327                                 foreach my $type (@types) {
2328                                         push @result, 
2329                                         {
2330                                                 'class' => $entry,
2331                                                 'type' => $type->{'type'},
2332                                                 'release' => $parent,
2333                                                 'state' => $type->{'state'},
2334                                         };
2335                                 }
2336                         }
2337                 }
2338         }
2340         return @result;
2343 sub deep_copy {
2344         my $this = shift;
2345         if (not ref $this) {
2346                 $this;
2347         } elsif (ref $this eq "ARRAY") {
2348                 [map deep_copy($_), @$this];
2349         } elsif (ref $this eq "HASH") {
2350                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2351         } else { die "what type is $_?" }
2355 sub session_run_result {
2356     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2357     $kernel->sig(CHLD => "child_reap");
2360 sub session_run_debug {
2361     my $result = $_[ARG0];
2362     print STDERR "$result\n";
2365 sub session_run_done {
2366     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2367     delete $heap->{task}->{$task_id};
2371 sub create_sources_list {
2372         my $session_id = shift;
2373         my $ldap_handle = &main::get_ldap_handle;
2374         my $result="/tmp/gosa_si_tmp_sources_list";
2376         # Remove old file
2377         if(stat($result)) {
2378                 unlink($result);
2379                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2380         }
2382         my $fh;
2383         open($fh, ">$result");
2384         if (not defined $fh) {
2385                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2386                 return undef;
2387         }
2388         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2389                 my $mesg=$ldap_handle->search(
2390                         base    => $main::ldap_server_dn,
2391                         scope   => 'base',
2392                         attrs   => 'FAIrepository',
2393                         filter  => 'objectClass=FAIrepositoryServer'
2394                 );
2395                 if($mesg->count) {
2396                         foreach my $entry(@{$mesg->{'entries'}}) {
2397                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2398                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2399                                         my $line = "deb $server $release";
2400                                         $sections =~ s/,/ /g;
2401                                         $line.= " $sections";
2402                                         print $fh $line."\n";
2403                                 }
2404                         }
2405                 }
2406         } else {
2407                 if (defined $main::ldap_server_dn){
2408                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2409                 } else {
2410                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2411                 }
2412         }
2413         close($fh);
2415         return $result;
2419 sub run_create_packages_list_db {
2420     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2421         my $session_id = $session->ID;
2423         my $task = POE::Wheel::Run->new(
2424                                         Priority => +20,
2425                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2426                                         StdoutEvent  => "session_run_result",
2427                                         StderrEvent  => "session_run_debug",
2428                                         CloseEvent   => "session_run_done",
2429                                         );
2430         $heap->{task}->{ $task->ID } = $task;
2434 sub create_packages_list_db {
2435         my ($ldap_handle, $sources_file, $session_id) = @_;
2436         
2437         # it should not be possible to trigger a recreation of packages_list_db
2438         # while packages_list_db is under construction, so set flag packages_list_under_construction
2439         # which is tested befor recreation can be started
2440         if (-r $packages_list_under_construction) {
2441                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2442                 return;
2443         } else {
2444                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2445                 # set packages_list_under_construction to true
2446                 system("touch $packages_list_under_construction");
2447                 @packages_list_statements=();
2448         }
2450         if (not defined $session_id) { $session_id = 0; }
2451         if (not defined $ldap_handle) { 
2452                 $ldap_handle= &get_ldap_handle();
2454                 if (not defined $ldap_handle) {
2455                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2456                         unlink($packages_list_under_construction);
2457                         return;
2458                 }
2459         }
2460         if (not defined $sources_file) { 
2461                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2462                 $sources_file = &create_sources_list($session_id);
2463         }
2465         if (not defined $sources_file) {
2466                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2467                 unlink($packages_list_under_construction);
2468                 return;
2469         }
2471         my $line;
2473         open(CONFIG, "<$sources_file") or do {
2474                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2475                 unlink($packages_list_under_construction);
2476                 return;
2477         };
2479         # Read lines
2480         while ($line = <CONFIG>){
2481                 # Unify
2482                 chop($line);
2483                 $line =~ s/^\s+//;
2484                 $line =~ s/^\s+/ /;
2486                 # Strip comments
2487                 $line =~ s/#.*$//g;
2489                 # Skip empty lines
2490                 if ($line =~ /^\s*$/){
2491                         next;
2492                 }
2494                 # Interpret deb line
2495                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2496                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2497                         my $section;
2498                         foreach $section (split(' ', $sections)){
2499                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2500                         }
2501                 }
2502         }
2504         close (CONFIG);
2506         find(\&cleanup_and_extract, keys( %repo_dirs ));
2507         &main::strip_packages_list_statements();
2508         unshift @packages_list_statements, "VACUUM";
2509         $packages_list_db->exec_statementlist(\@packages_list_statements);
2510         unlink($packages_list_under_construction);
2511         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2512         return;
2515 # This function should do some intensive task to minimize the db-traffic
2516 sub strip_packages_list_statements {
2517     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2518         my @new_statement_list=();
2519         my $hash;
2520         my $insert_hash;
2521         my $update_hash;
2522         my $delete_hash;
2523         my $local_timestamp=get_time();
2525         foreach my $existing_entry (@existing_entries) {
2526                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2527         }
2529         foreach my $statement (@packages_list_statements) {
2530                 if($statement =~ /^INSERT/i) {
2531                         # Assign the values from the insert statement
2532                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2533                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2534                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2535                                 # If section or description has changed, update the DB
2536                                 if( 
2537                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2538                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2539                                 ) {
2540                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2541                                 }
2542                         } else {
2543                                 # Insert a non-existing entry to db
2544                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2545                         }
2546                 } elsif ($statement =~ /^UPDATE/i) {
2547                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2548                         /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2549                         foreach my $distribution (keys %{$hash}) {
2550                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2551                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2552                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2553                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2554                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2555                                                 my $section;
2556                                                 my $description;
2557                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2558                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2559                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2560                                                 }
2561                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2562                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2563                                                 }
2564                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2565                                         }
2566                                 }
2567                         }
2568                 }
2569         }
2571         # TODO: Check for orphaned entries
2573         # unroll the insert_hash
2574         foreach my $distribution (keys %{$insert_hash}) {
2575                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2576                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2577                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2578                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2579                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2580                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2581                                 ."'$local_timestamp')";
2582                         }
2583                 }
2584         }
2586         # unroll the update hash
2587         foreach my $distribution (keys %{$update_hash}) {
2588                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2589                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2590                                 my $set = "";
2591                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2592                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2593                                 }
2594                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2595                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2596                                 }
2597                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2598                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2599                                 }
2600                                 if(defined($set) and length($set) > 0) {
2601                                         $set .= "timestamp = '$local_timestamp'";
2602                                 } else {
2603                                         next;
2604                                 }
2605                                 push @new_statement_list, 
2606                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2607                                         ." distribution = '$distribution'"
2608                                         ." AND package = '$package'"
2609                                         ." AND version = '$version'";
2610                         }
2611                 }
2612         }
2614         @packages_list_statements = @new_statement_list;
2618 sub parse_package_info {
2619     my ($baseurl, $dist, $section, $session_id)= @_;
2620     my ($package);
2621     if (not defined $session_id) { $session_id = 0; }
2622     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2623     $repo_dirs{ "${repo_path}/pool" } = 1;
2625     foreach $package ("Packages.gz"){
2626         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2627         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2628         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2629     }
2630     
2634 sub get_package {
2635     my ($url, $dest, $session_id)= @_;
2636     if (not defined $session_id) { $session_id = 0; }
2638     my $tpath = dirname($dest);
2639     -d "$tpath" || mkpath "$tpath";
2641     # This is ugly, but I've no time to take a look at "how it works in perl"
2642     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2643         system("gunzip -cd '$dest' > '$dest.in'");
2644         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2645         unlink($dest);
2646         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2647     } else {
2648         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2649     }
2650     return 0;
2654 sub parse_package {
2655     my ($path, $dist, $srv_path, $session_id)= @_;
2656     if (not defined $session_id) { $session_id = 0;}
2657     my ($package, $version, $section, $description);
2658     my $PACKAGES;
2659     my $timestamp = &get_time();
2661     if(not stat("$path.in")) {
2662         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2663         return;
2664     }
2666     open($PACKAGES, "<$path.in");
2667     if(not defined($PACKAGES)) {
2668         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2669         return;
2670     }
2672     # Read lines
2673     while (<$PACKAGES>){
2674         my $line = $_;
2675         # Unify
2676         chop($line);
2678         # Use empty lines as a trigger
2679         if ($line =~ /^\s*$/){
2680             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2681             push(@packages_list_statements, $sql);
2682             $package = "none";
2683             $version = "none";
2684             $section = "none";
2685             $description = "none"; 
2686             next;
2687         }
2689         # Trigger for package name
2690         if ($line =~ /^Package:\s/){
2691             ($package)= ($line =~ /^Package: (.*)$/);
2692             next;
2693         }
2695         # Trigger for version
2696         if ($line =~ /^Version:\s/){
2697             ($version)= ($line =~ /^Version: (.*)$/);
2698             next;
2699         }
2701         # Trigger for description
2702         if ($line =~ /^Description:\s/){
2703             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2704             next;
2705         }
2707         # Trigger for section
2708         if ($line =~ /^Section:\s/){
2709             ($section)= ($line =~ /^Section: (.*)$/);
2710             next;
2711         }
2713         # Trigger for filename
2714         if ($line =~ /^Filename:\s/){
2715             my ($filename) = ($line =~ /^Filename: (.*)$/);
2716             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2717             next;
2718         }
2719     }
2721     close( $PACKAGES );
2722     unlink( "$path.in" );
2723     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2727 sub store_fileinfo {
2728     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2730     my %fileinfo = (
2731         'package' => $package,
2732         'dist' => $dist,
2733         'version' => $vers,
2734     );
2736     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2740 sub cleanup_and_extract {
2741     my $fileinfo = $repo_files{ $File::Find::name };
2743     if( defined $fileinfo ) {
2745         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2746         my $sql;
2747         my $package = $fileinfo->{ 'package' };
2748         my $newver = $fileinfo->{ 'version' };
2750         mkpath($dir);
2751         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2753                 if( -f "$dir/DEBIAN/templates" ) {
2755                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2757                         my $tmpl= "";
2758                         {
2759                                 local $/=undef;
2760                                 open FILE, "$dir/DEBIAN/templates";
2761                                 $tmpl = &encode_base64(<FILE>);
2762                                 close FILE;
2763                         }
2764                         rmtree("$dir/DEBIAN/templates");
2766                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2767                 push @packages_list_statements, $sql;
2768                 }
2769     }
2771     return;
2775 sub register_at_foreign_servers {   
2776     my ($kernel) = $_[KERNEL];
2778     # hole alle bekannten server aus known_server_db
2779     my $server_sql = "SELECT * FROM $known_server_tn";
2780     my $server_res = $known_server_db->exec_statement($server_sql);
2782     # no entries in known_server_db
2783     if (not ref(@$server_res[0]) eq "ARRAY") { 
2784         # TODO
2785     }
2787     # detect already connected clients
2788     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2789     my $client_res = $known_clients_db->exec_statement($client_sql);
2791     # send my server details to all other gosa-si-server within the network
2792     foreach my $hit (@$server_res) {
2793         my $hostname = @$hit[0];
2794         my $hostkey = &create_passwd;
2796         # add already connected clients to registration message 
2797         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2798         &add_content2xml_hash($myhash, 'key', $hostkey);
2799         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2800         
2801         # build registration message and send it
2802         my $foreign_server_msg = &create_xml_string($myhash);
2803         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2804     }
2805     
2806     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2807     return;
2811 #==== MAIN = main ==============================================================
2812 #  parse commandline options
2813 Getopt::Long::Configure( "bundling" );
2814 GetOptions("h|help" => \&usage,
2815         "c|config=s" => \$cfg_file,
2816         "f|foreground" => \$foreground,
2817         "v|verbose+" => \$verbose,
2818         "no-bus+" => \$no_bus,
2819         "no-arp+" => \$no_arp,
2820            );
2822 #  read and set config parameters
2823 &check_cmdline_param ;
2824 &read_configfile;
2825 &check_pid;
2827 $SIG{CHLD} = 'IGNORE';
2829 # forward error messages to logfile
2830 if( ! $foreground ) {
2831   open( STDIN,  '+>/dev/null' );
2832   open( STDOUT, '+>&STDIN'    );
2833   open( STDERR, '+>&STDIN'    );
2836 # Just fork, if we are not in foreground mode
2837 if( ! $foreground ) { 
2838     chdir '/'                 or die "Can't chdir to /: $!";
2839     $pid = fork;
2840     setsid                    or die "Can't start a new session: $!";
2841     umask 0;
2842 } else { 
2843     $pid = $$; 
2846 # Do something useful - put our PID into the pid_file
2847 if( 0 != $pid ) {
2848     open( LOCK_FILE, ">$pid_file" );
2849     print LOCK_FILE "$pid\n";
2850     close( LOCK_FILE );
2851     if( !$foreground ) { 
2852         exit( 0 ) 
2853     };
2856 # parse head url and revision from svn
2857 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2858 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2859 $server_headURL = defined $1 ? $1 : 'unknown' ;
2860 $server_revision = defined $2 ? $2 : 'unknown' ;
2861 if ($server_headURL =~ /\/tag\// || 
2862         $server_headURL =~ /\/branches\// ) {
2863     $server_status = "stable"; 
2864 } else {
2865     $server_status = "developmental" ;
2869 daemon_log(" ", 1);
2870 daemon_log("$0 started!", 1);
2871 daemon_log("status: $server_status", 1);
2872 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2874 if ($no_bus > 0) {
2875     $bus_activ = "false"
2878 # connect to incoming_db
2879 unlink($incoming_file_name);
2880 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2881 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2883 # connect to gosa-si job queue
2884 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2885 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2887 # connect to known_clients_db
2888 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2889 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2891 # connect to foreign_clients_db
2892 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2893 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2895 # connect to known_server_db
2896 unlink($known_server_file_name);
2897 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2898 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2900 # connect to login_usr_db
2901 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2902 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2904 # connect to fai_server_db and fai_release_db
2905 unlink($fai_server_file_name);
2906 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2907 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2909 unlink($fai_release_file_name);
2910 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2911 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2913 # connect to packages_list_db
2914 #unlink($packages_list_file_name);
2915 unlink($packages_list_under_construction);
2916 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2917 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2919 # connect to messaging_db
2920 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2921 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2924 # create xml object used for en/decrypting
2925 $xml = new XML::Simple();
2928 # foreign servers 
2929 my @foreign_server_list;
2931 # add foreign server from cfg file
2932 if ($foreign_server_string ne "") {
2933     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2934     foreach my $foreign_server (@cfg_foreign_server_list) {
2935         push(@foreign_server_list, $foreign_server);
2936     }
2939 # add foreign server from dns
2940 my @tmp_servers;
2941 if ( !$server_domain) {
2942     # Try our DNS Searchlist
2943     for my $domain(get_dns_domains()) {
2944         chomp($domain);
2945         my @tmp_domains= &get_server_addresses($domain);
2946         if(@tmp_domains) {
2947             for my $tmp_server(@tmp_domains) {
2948                 push @tmp_servers, $tmp_server;
2949             }
2950         }
2951     }
2952     if(@tmp_servers && length(@tmp_servers)==0) {
2953         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2954     }
2955 } else {
2956     @tmp_servers = &get_server_addresses($server_domain);
2957     if( 0 == @tmp_servers ) {
2958         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2959     }
2961 foreach my $server (@tmp_servers) { 
2962     unshift(@foreign_server_list, $server); 
2964 # eliminate duplicate entries
2965 @foreign_server_list = &del_doubles(@foreign_server_list);
2966 my $all_foreign_server = join(", ", @foreign_server_list);
2967 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2969 # add all found foreign servers to known_server
2970 my $act_timestamp = &get_time();
2971 foreach my $foreign_server (@foreign_server_list) {
2972     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2973             primkey=>['hostname'],
2974             hostname=>$foreign_server,
2975             status=>'not_jet_registered',
2976             hostkey=>"none",
2977             timestamp=>$act_timestamp,
2978             } );
2982 POE::Component::Server::TCP->new(
2983     Alias => "TCP_SERVER",
2984         Port => $server_port,
2985         ClientInput => sub {
2986         my ($kernel, $input) = @_[KERNEL, ARG0];
2987         push(@tasks, $input);
2988         push(@msgs_to_decrypt, $input);
2989         $kernel->yield("msg_to_decrypt");
2990         },
2991     InlineStates => {
2992         msg_to_decrypt => \&msg_to_decrypt,
2993         next_task => \&next_task,
2994         task_result => \&handle_task_result,
2995         task_done   => \&handle_task_done,
2996         task_debug  => \&handle_task_debug,
2997         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2998     }
2999 );
3001 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3003 # create session for repeatedly checking the job queue for jobs
3004 POE::Session->create(
3005         inline_states => {
3006                 _start => \&session_start,
3007         register_at_foreign_servers => \&register_at_foreign_servers,
3008         sig_handler => \&sig_handler,
3009         next_task => \&next_task,
3010         task_result => \&handle_task_result,
3011         task_done   => \&handle_task_done,
3012         task_debug  => \&handle_task_debug,
3013         watch_for_next_tasks => \&watch_for_next_tasks,
3014         watch_for_new_messages => \&watch_for_new_messages,
3015         watch_for_delivery_messages => \&watch_for_delivery_messages,
3016         watch_for_done_messages => \&watch_for_done_messages,
3017                 watch_for_new_jobs => \&watch_for_new_jobs,
3018         watch_for_done_jobs => \&watch_for_done_jobs,
3019         watch_for_old_known_clients => \&watch_for_old_known_clients,
3020         create_packages_list_db => \&run_create_packages_list_db,
3021         create_fai_server_db => \&run_create_fai_server_db,
3022         create_fai_release_db => \&run_create_fai_release_db,
3023         session_run_result => \&session_run_result,
3024         session_run_debug => \&session_run_debug,
3025         session_run_done => \&session_run_done,
3026         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3027         }
3028 );
3031 # import all modules
3032 &import_modules;
3034 # TODO
3035 # check wether all modules are gosa-si valid passwd check
3039 POE::Kernel->run();
3040 exit;