Code

* gosa-si-server-nobus
[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);
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_bus = 0;
115 $bus_activ = "true";
116 $no_arp = 0;
117 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
118 my @packages_list_statements;
119 my $watch_for_new_jobs_in_progress = 0;
121 # holds all incoming decrypted messages
122 our $incoming_db;
123 our $incoming_tn = 'incoming';
124 my $incoming_file_name;
125 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
126         "timestamp DEFAULT 'none'", 
127         "headertag DEFAULT 'none'",
128                 "targettag DEFAULT 'none'",
129         "xmlmessage DEFAULT 'none'",
130         "module DEFAULT 'none'",
131         "sessionid DEFAULT '0'",
132         );
134 # holds all gosa jobs
135 our $job_db;
136 our $job_queue_tn = 'jobs';
137 my $job_queue_file_name;
138 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
139                 "timestamp DEFAULT 'none'", 
140                 "status DEFAULT 'none'", 
141                 "result DEFAULT 'none'", 
142                 "progress DEFAULT 'none'", 
143         "headertag DEFAULT 'none'", 
144                 "targettag DEFAULT 'none'", 
145                 "xmlmessage DEFAULT 'none'", 
146                 "macaddress DEFAULT 'none'",
147                 "plainname DEFAULT 'none'",
148                 );
150 # holds all other gosa-sd as well as the gosa-sd-bus
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients"; 
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client 
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server"; 
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release"; 
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386"; 
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging"; 
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
197         "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai"; 
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 %cfg_defaults = (
210 "general" => {
211     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
212     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
213     },
214 "bus" => {
215     "activ" => [\$bus_activ, "true"],
216     },
217 "server" => {
218     "port" => [\$server_port, "20081"],
219     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
229     "repo-path"            => [\$repo_path, '/srv/www/repository'],
230     "ldap-uri"             => [\$ldap_uri, ""],
231     "ldap-base"            => [\$ldap_base, ""],
232     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
233     "ldap-admin-password"  => [\$ldap_admin_password, ""],
234     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
235     "max-clients"          => [\$max_clients, 10],
236     },
237 "GOsaPackages" => {
238     "ip" => [\$gosa_ip, "0.0.0.0"],
239     "port" => [\$gosa_port, "20082"],
240     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
241     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
242     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
243     "key" => [\$GosaPackages_key, "none"],
244     },
245 "ClientPackages" => {
246     "key" => [\$ClientPackages_key, "none"],
247     },
248 "ServerPackages"=> {
249     "address"      => [\$foreign_server_string, ""],
250     "domain"  => [\$server_domain, ""],
251     "key"     => [\$ServerPackages_key, "none"],
252     "key-lifetime" => [\$foreign_servers_register_delay, 120],
254 );
257 #===  FUNCTION  ================================================================
258 #         NAME:  usage
259 #   PARAMETERS:  nothing
260 #      RETURNS:  nothing
261 #  DESCRIPTION:  print out usage text to STDERR
262 #===============================================================================
263 sub usage {
264     print STDERR << "EOF" ;
265 usage: $prg [-hvf] [-c config]
267            -h        : this (help) message
268            -c <file> : config file
269            -f        : foreground, process will not be forked to background
270            -v        : be verbose (multiple to increase verbosity)
271            -no-bus   : starts $prg without connection to bus
272            -no-arp   : starts $prg without connection to arp module
273  
274 EOF
275     print "\n" ;
279 #===  FUNCTION  ================================================================
280 #         NAME:  read_configfile
281 #   PARAMETERS:  cfg_file - string -
282 #      RETURNS:  nothing
283 #  DESCRIPTION:  read cfg_file and set variables
284 #===============================================================================
285 sub read_configfile {
286     my $cfg;
287     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
288         if( -r $cfg_file ) {
289             $cfg = Config::IniFiles->new( -file => $cfg_file );
290         } else {
291             print STDERR "Couldn't read config file!\n";
292         }
293     } else {
294         $cfg = Config::IniFiles->new() ;
295     }
296     foreach my $section (keys %cfg_defaults) {
297         foreach my $param (keys %{$cfg_defaults{ $section }}) {
298             my $pinfo = $cfg_defaults{ $section }{ $param };
299             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
300         }
301     }
305 #===  FUNCTION  ================================================================
306 #         NAME:  logging
307 #   PARAMETERS:  level - string - default 'info'
308 #                msg - string -
309 #                facility - string - default 'LOG_DAEMON'
310 #      RETURNS:  nothing
311 #  DESCRIPTION:  function for logging
312 #===============================================================================
313 sub daemon_log {
314     # log into log_file
315     my( $msg, $level ) = @_;
316     if(not defined $msg) { return }
317     if(not defined $level) { $level = 1 }
318     if(defined $log_file){
319         open(LOG_HANDLE, ">>$log_file");
320         chmod 0600, $log_file;
321         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
322             print STDERR "cannot open $log_file: $!";
323             return 
324         }
325         chomp($msg);
326         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
327         if($level <= $verbose){
328             my ($seconds, $minutes, $hours, $monthday, $month,
329                     $year, $weekday, $yearday, $sommertime) = localtime(time);
330             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
331             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
332             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
333             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
334             $month = $monthnames[$month];
335             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
336             $year+=1900;
337             my $name = $prg;
339             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
340             print LOG_HANDLE $log_msg;
341             if( $foreground ) { 
342                 print STDERR $log_msg;
343             }
344         }
345         close( LOG_HANDLE );
346     }
350 #===  FUNCTION  ================================================================
351 #         NAME:  check_cmdline_param
352 #   PARAMETERS:  nothing
353 #      RETURNS:  nothing
354 #  DESCRIPTION:  validates commandline parameter
355 #===============================================================================
356 sub check_cmdline_param () {
357     my $err_config;
358     my $err_counter = 0;
359         if(not defined($cfg_file)) {
360                 $cfg_file = "/etc/gosa-si/server.conf";
361                 if(! -r $cfg_file) {
362                         $err_config = "please specify a config file";
363                         $err_counter += 1;
364                 }
365     }
366     if( $err_counter > 0 ) {
367         &usage( "", 1 );
368         if( defined( $err_config)) { print STDERR "$err_config\n"}
369         print STDERR "\n";
370         exit( -1 );
371     }
375 #===  FUNCTION  ================================================================
376 #         NAME:  check_pid
377 #   PARAMETERS:  nothing
378 #      RETURNS:  nothing
379 #  DESCRIPTION:  handels pid processing
380 #===============================================================================
381 sub check_pid {
382     $pid = -1;
383     # Check, if we are already running
384     if( open(LOCK_FILE, "<$pid_file") ) {
385         $pid = <LOCK_FILE>;
386         if( defined $pid ) {
387             chomp( $pid );
388             if( -f "/proc/$pid/stat" ) {
389                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
390                 if( $stat ) {
391                                         daemon_log("ERROR: Already running",1);
392                     close( LOCK_FILE );
393                     exit -1;
394                 }
395             }
396         }
397         close( LOCK_FILE );
398         unlink( $pid_file );
399     }
401     # create a syslog msg if it is not to possible to open PID file
402     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
403         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
404         if (open(LOCK_FILE, '<', $pid_file)
405                 && ($pid = <LOCK_FILE>))
406         {
407             chomp($pid);
408             $msg .= "(PID $pid)\n";
409         } else {
410             $msg .= "(unable to read PID)\n";
411         }
412         if( ! ($foreground) ) {
413             openlog( $0, "cons,pid", "daemon" );
414             syslog( "warning", $msg );
415             closelog();
416         }
417         else {
418             print( STDERR " $msg " );
419         }
420         exit( -1 );
421     }
424 #===  FUNCTION  ================================================================
425 #         NAME:  import_modules
426 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
427 #                are stored
428 #      RETURNS:  nothing
429 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
430 #                state is on is imported by "require 'file';"
431 #===============================================================================
432 sub import_modules {
433     daemon_log(" ", 1);
435     if (not -e $modules_path) {
436         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
437     }
439     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
440     while (defined (my $file = readdir (DIR))) {
441         if (not $file =~ /(\S*?).pm$/) {
442             next;
443         }
444                 my $mod_name = $1;
446         if( $file =~ /ArpHandler.pm/ ) {
447             if( $no_arp > 0 ) {
448                 next;
449             }
450         }
451         
452         eval { require $file; };
453         if ($@) {
454             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
455             daemon_log("$@", 5);
456                 } else {
457                         my $info = eval($mod_name.'::get_module_info()');
458                         # Only load module if get_module_info() returns a non-null object
459                         if( $info ) {
460                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
461                                 $known_modules->{$mod_name} = $info;
462                                 daemon_log("0 INFO: module $mod_name loaded", 5);
463                         }
464                 }
465     }   
466     close (DIR);
470 #===  FUNCTION  ================================================================
471 #         NAME:  sig_int_handler
472 #   PARAMETERS:  signal - string - signal arose from system
473 #      RETURNS:  noting
474 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
475 #===============================================================================
476 sub sig_int_handler {
477     my ($signal) = @_;
479 #       if (defined($ldap_handle)) {
480 #               $ldap_handle->disconnect;
481 #       }
482     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
483     
485     daemon_log("shutting down gosa-si-server", 1);
486     system("kill `ps -C gosa-si-server -o pid=`");
488 $SIG{INT} = \&sig_int_handler;
491 sub check_key_and_xml_validity {
492     my ($crypted_msg, $module_key, $session_id) = @_;
493     my $msg;
494     my $msg_hash;
495     my $error_string;
496     eval{
497         $msg = &decrypt_msg($crypted_msg, $module_key);
499         if ($msg =~ /<xml>/i){
500             $msg =~ s/\s+/ /g;  # just for better daemon_log
501             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
502             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
504             ##############
505             # check header
506             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
507             my $header_l = $msg_hash->{'header'};
508             if( 1 > @{$header_l} ) { die 'empty header tag'; }
509             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
510             my $header = @{$header_l}[0];
511             if( 0 == length $header) { die 'empty string in header tag'; }
513             ##############
514             # check source
515             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
516             my $source_l = $msg_hash->{'source'};
517             if( 1 > @{$source_l} ) { die 'empty source tag'; }
518             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
519             my $source = @{$source_l}[0];
520             if( 0 == length $source) { die 'source error'; }
522             ##############
523             # check target
524             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
525             my $target_l = $msg_hash->{'target'};
526             if( 1 > @{$target_l} ) { die 'empty target tag'; }
527         }
528     };
529     if($@) {
530         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
531         $msg = undef;
532         $msg_hash = undef;
533     }
535     return ($msg, $msg_hash);
539 sub check_outgoing_xml_validity {
540     my ($msg) = @_;
542     my $msg_hash;
543     eval{
544         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
546         ##############
547         # check header
548         my $header_l = $msg_hash->{'header'};
549         if( 1 != @{$header_l} ) {
550             die 'no or more than one headers specified';
551         }
552         my $header = @{$header_l}[0];
553         if( 0 == length $header) {
554             die 'header has length 0';
555         }
557         ##############
558         # check source
559         my $source_l = $msg_hash->{'source'};
560         if( 1 != @{$source_l} ) {
561             die 'no or more than 1 sources specified';
562         }
563         my $source = @{$source_l}[0];
564         if( 0 == length $source) {
565             die 'source has length 0';
566         }
567         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
568                 $source =~ /^GOSA$/i ) {
569             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
570         }
571         
572         ##############
573         # check target  
574         my $target_l = $msg_hash->{'target'};
575         if( 0 == @{$target_l} ) {
576             die "no targets specified";
577         }
578         foreach my $target (@$target_l) {
579             if( 0 == length $target) {
580                 die "target has length 0";
581             }
582             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
583                     $target =~ /^GOSA$/i ||
584                     $target =~ /^\*$/ ||
585                     $target =~ /KNOWN_SERVER/i ||
586                     $target =~ /JOBDB/i ||
587                     $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 ){
588                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
589             }
590         }
591     };
592     if($@) {
593         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
594         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
595         $msg_hash = undef;
596     }
598     return ($msg_hash);
602 sub input_from_known_server {
603     my ($input, $remote_ip, $session_id) = @_ ;  
604     my ($msg, $msg_hash, $module);
606     my $sql_statement= "SELECT * FROM known_server";
607     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
609     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
610         my $host_name = $hit->{hostname};
611         if( not $host_name =~ "^$remote_ip") {
612             next;
613         }
614         my $host_key = $hit->{hostkey};
615         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
616         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
618         # check if module can open msg envelope with module key
619         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
620         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
621             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
622             daemon_log("$@", 8);
623             next;
624         }
625         else {
626             $msg = $tmp_msg;
627             $msg_hash = $tmp_msg_hash;
628             $module = "ServerPackages";
629             last;
630         }
631     }
633     if( (!$msg) || (!$msg_hash) || (!$module) ) {
634         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
635     }
636   
637     return ($msg, $msg_hash, $module);
641 sub input_from_known_client {
642     my ($input, $remote_ip, $session_id) = @_ ;  
643     my ($msg, $msg_hash, $module);
645     my $sql_statement= "SELECT * FROM known_clients";
646     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
647     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
648         my $host_name = $hit->{hostname};
649         if( not $host_name =~ /^$remote_ip:\d*$/) {
650                 next;
651                 }
652         my $host_key = $hit->{hostkey};
653         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
654         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
656         # check if module can open msg envelope with module key
657         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
659         if( (!$msg) || (!$msg_hash) ) {
660             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
661             &daemon_log("$@", 8);
662             next;
663         }
664         else {
665             $module = "ClientPackages";
666             last;
667         }
668     }
670     if( (!$msg) || (!$msg_hash) || (!$module) ) {
671         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
672     }
674     return ($msg, $msg_hash, $module);
678 sub input_from_unknown_host {
679     no strict "refs";
680     my ($input, $session_id) = @_ ;
681     my ($msg, $msg_hash, $module);
682     my $error_string;
683     
684         my %act_modules = %$known_modules;
685         
686     while( my ($mod, $info) = each(%act_modules)) {
688         # check a key exists for this module
689         my $module_key = ${$mod."_key"};
690         if( not defined $module_key ) {
691             if( $mod eq 'ArpHandler' ) {
692                 next;
693             }
694             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
695             next;
696         }
697         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
699         # check if module can open msg envelope with module key
700         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
701         if( (not defined $msg) || (not defined $msg_hash) ) {
702             next;
703         }
704         else {
705             $module = $mod;
706             last;
707         }
708     }
710     if( (!$msg) || (!$msg_hash) || (!$module)) {
711         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
712     }
714     return ($msg, $msg_hash, $module);
718 sub create_ciphering {
719     my ($passwd) = @_;
720         if((!defined($passwd)) || length($passwd)==0) {
721                 $passwd = "";
722         }
723     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
724     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
725     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
726     $my_cipher->set_iv($iv);
727     return $my_cipher;
731 sub encrypt_msg {
732     my ($msg, $key) = @_;
733     my $my_cipher = &create_ciphering($key);
734     my $len;
735     {
736             use bytes;
737             $len= 16-length($msg)%16;
738     }
739     $msg = "\0"x($len).$msg;
740     $msg = $my_cipher->encrypt($msg);
741     chomp($msg = &encode_base64($msg));
742     # there are no newlines allowed inside msg
743     $msg=~ s/\n//g;
744     return $msg;
748 sub decrypt_msg {
750     my ($msg, $key) = @_ ;
751     $msg = &decode_base64($msg);
752     my $my_cipher = &create_ciphering($key);
753     $msg = $my_cipher->decrypt($msg); 
754     $msg =~ s/\0*//g;
755     return $msg;
759 sub get_encrypt_key {
760     my ($target) = @_ ;
761     my $encrypt_key;
762     my $error = 0;
764     # target can be in known_server
765     if( not defined $encrypt_key ) {
766         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
767         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
768         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
769             my $host_name = $hit->{hostname};
770             if( $host_name ne $target ) {
771                 next;
772             }
773             $encrypt_key = $hit->{hostkey};
774             last;
775         }
776     }
778     # target can be in known_client
779     if( not defined $encrypt_key ) {
780         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
781         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
782         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
783             my $host_name = $hit->{hostname};
784             if( $host_name ne $target ) {
785                 next;
786             }
787             $encrypt_key = $hit->{hostkey};
788             last;
789         }
790     }
792     return $encrypt_key;
796 #===  FUNCTION  ================================================================
797 #         NAME:  open_socket
798 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
799 #                [PeerPort] string necessary if port not appended by PeerAddr
800 #      RETURNS:  socket IO::Socket::INET
801 #  DESCRIPTION:  open a socket to PeerAddr
802 #===============================================================================
803 sub open_socket {
804     my ($PeerAddr, $PeerPort) = @_ ;
805     if(defined($PeerPort)){
806         $PeerAddr = $PeerAddr.":".$PeerPort;
807     }
808     my $socket;
809     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
810             Porto => "tcp",
811             Type => SOCK_STREAM,
812             Timeout => 5,
813             );
814     if(not defined $socket) {
815         return;
816     }
817 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
818     return $socket;
822 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
823 #===  FUNCTION  ================================================================
824 #         NAME:  get_ip 
825 #   PARAMETERS:  interface name (i.e. eth0)
826 #      RETURNS:  (ip address) 
827 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
828 #===============================================================================
829 #sub get_ip {
830 #       my $ifreq= shift;
831 #       my $result= "";
832 #       my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
833 #       my $proto= getprotobyname('ip');
835 #       socket SOCKET, PF_INET, SOCK_DGRAM, $proto
836 #               or die "socket: $!";
838 #       if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
839 #               my ($if, $sin)    = unpack 'a16 a16', $ifreq;
840 #               my ($port, $addr) = sockaddr_in $sin;
841 #               my $ip            = inet_ntoa $addr;
843 #               if ($ip && length($ip) > 0) {
844 #                       $result = $ip;
845 #               }
846 #       }
848 #       return $result;
849 #}
852 sub get_local_ip_for_remote_ip {
853         my $remote_ip= shift;
854         my $result="0.0.0.0";
856         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
857                 if($remote_ip eq "127.0.0.1") {
858                         $result = "127.0.0.1";
859                 } else {
860                         my $PROC_NET_ROUTE= ('/proc/net/route');
862                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
863                                 or die "Could not open $PROC_NET_ROUTE";
865                         my @ifs = <PROC_NET_ROUTE>;
867                         close(PROC_NET_ROUTE);
869                         # Eat header line
870                         shift @ifs;
871                         chomp @ifs;
872                         foreach my $line(@ifs) {
873                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
874                                 my $destination;
875                                 my $mask;
876                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
877                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
879                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
880                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
881                                         # destination matches route, save mac and exit
882                                         $result= &get_ip($Iface);
883                                         last;
884                                 }
885                         }
886                 }
887         } else {
888                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
889         }
890         return $result;
894 sub send_msg_to_target {
895     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
896     my $error = 0;
897     my $header;
898     my $timestamp = &get_time();
899     my $new_status;
900     my $act_status;
901     my ($sql_statement, $res);
902   
903     if( $msg_header ) {
904         $header = "'$msg_header'-";
905     } else {
906         $header = "";
907     }
909         # Patch the source ip
910         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
911                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
912                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
913         }
915     # encrypt xml msg
916     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
918     # opensocket
919     my $socket = &open_socket($address);
920     if( !$socket ) {
921         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
922         $error++;
923     }
924     
925     if( $error == 0 ) {
926         # send xml msg
927         print $socket $crypted_msg."\n";
929         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
930         daemon_log("$session_id DEBUG: message:\n$msg", 9);
931         
932     }
934     # close socket in any case
935     if( $socket ) {
936         close $socket;
937     }
939     if( $error > 0 ) { $new_status = "down"; }
940     else { $new_status = $msg_header; }
943     # known_clients
944     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
945     $res = $known_clients_db->select_dbentry($sql_statement);
946     if( keys(%$res) == 1) {
947         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
948         if ($act_status eq "down" && $new_status eq "down") {
949             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
950             $res = $known_clients_db->del_dbentry($sql_statement);
951             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
952         } else { 
953             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
954             $res = $known_clients_db->update_dbentry($sql_statement);
955             if($new_status eq "down"){
956                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957             } else {
958                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
959             }
960         }
961     }
963     # known_server
964     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
965     $res = $known_server_db->select_dbentry($sql_statement);
966     if( keys(%$res) == 1) {
967         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
968         if ($act_status eq "down" && $new_status eq "down") {
969             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
970             $res = $known_server_db->del_dbentry($sql_statement);
971             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
972         } 
973         else { 
974             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
975             $res = $known_server_db->update_dbentry($sql_statement);
976             if($new_status eq "down"){
977                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
978             } else {
979                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
980             }
981         }
982     }
983     return $error; 
987 sub update_jobdb_status_for_send_msgs {
988     my ($answer, $error) = @_;
989     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
990         my $jobdb_id = $1;
991             
992         # sending msg faild
993         if( $error ) {
994             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
995                 my $sql_statement = "UPDATE $job_queue_tn ".
996                     "SET status='error', result='can not deliver msg, please consult log file' ".
997                     "WHERE id=$jobdb_id";
998                 my $res = $job_db->update_dbentry($sql_statement);
999             }
1001         # sending msg was successful
1002         } else {
1003             my $sql_statement = "UPDATE $job_queue_tn ".
1004                 "SET status='done' ".
1005                 "WHERE id=$jobdb_id AND status='processed'";
1006             my $res = $job_db->update_dbentry($sql_statement);
1007         }
1008     }
1012 sub sig_handler {
1013         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1014         daemon_log("0 INFO got signal '$signal'", 1); 
1015         $kernel->sig_handled();
1016         return;
1020 sub msg_to_decrypt {
1021     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1022     my $session_id = $session->ID;
1023     my ($msg, $msg_hash, $module);
1024     my $error = 0;
1026     # hole neue msg aus @msgs_to_decrypt
1027     my $next_msg = shift @msgs_to_decrypt;
1028     
1029     # entschlüssle sie
1031     # msg is from a new client or gosa
1032     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1033     # msg is from a gosa-si-server or gosa-si-bus
1034     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1035         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1036     }
1037     # msg is from a gosa-si-client
1038     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1039         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1040     }
1041     # an error occurred
1042     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1043         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1044         # could not understand a msg from its server the client cause a re-registering process
1045         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1046                 "' to cause a re-registering of the client if necessary", 5);
1047         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1048         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1049         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1050             my $host_name = $hit->{'hostname'};
1051             my $host_key = $hit->{'hostkey'};
1052             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1053             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1054             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1055         }
1056         $error++;
1057     }
1060     my $header;
1061     my $target;
1062     my $source;
1063     my $done = 0;
1064     my $sql;
1065     my $res;
1066     # check whether this message should be processed here
1067     if ($error == 0) {
1068         $header = @{$msg_hash->{'header'}}[0];
1069         $target = @{$msg_hash->{'target'}}[0];
1070         $source = @{$msg_hash->{'source'}}[0];
1071         my ($target_ip, $target_port) = split(':', $target);
1072         my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1074         # target and source is equal to GOSA -> process here
1075         if (not $done) {
1076             if ($target eq "GOSA" && $source eq "GOSA") {
1077                 $done = 1;                    
1078             }
1079         }
1081         # target is own address without forward_to_gosa-tag -> process here
1082         if (not $done) {
1083             if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1084                 $done = 1;
1085                 if ($source eq "GOSA") {
1086                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1087                 }
1088                 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1089             }
1090         }
1092         # target is a client address in known_clients -> process here
1093         if (not $done) {
1094             $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1095             $res = $known_clients_db->select_dbentry($sql);
1096             if (keys(%$res) > 0) {
1097                 $done = 1; 
1098                 my $hostname = $res->{1}->{'hostname'};
1099                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1100                 print STDERR "target is a client address in known_clients -> process here\n";
1101             }
1102         }
1104         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1105         if (not $done) {
1106             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1107             my $gosa_at;
1108             my $gosa_session_id;
1109             if (($target eq $server_address) && (defined $forward_to_gosa)){
1110                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1111                 if ($gosa_at ne $server_address) {
1112                     $done = 1;
1113                     print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1114                 }
1115             }
1116         }
1118         # if message should be processed here -> add message to incoming_db
1119         if ($done) {
1121             # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1122             # so gosa-si-server knows how to process this kind of messages
1123             if ($header =~ /^gosa_/ || $header =~ /job_/) {
1124                 $module = "GosaPackages";
1125             }
1127             my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1128                     primkey=>[],
1129                     headertag=>$header,
1130                     targettag=>$target,
1131                     xmlmessage=>$msg,
1132                     timestamp=>&get_time,
1133                     module=>$module,
1134                     sessionid=>$session_id,
1135                     } );
1137         }
1139         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1140         if (not $done) {
1141             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1142             my $gosa_at;
1143             my $gosa_session_id;
1144             if (($target eq $server_address) && (defined $forward_to_gosa)){
1145                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1146                 if ($gosa_at eq $server_address) {
1147                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1148                     if( defined $session_reference ) {
1149                         $heap = $session_reference->get_heap();
1150                     }
1151                     if(exists $heap->{'client'}) {
1152                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1153                         $heap->{'client'}->put($msg);
1154                     }
1155                     $done = 1;
1156                     print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1157                 }
1158             }
1160         }
1162         # target is a client address in foreign_clients -> forward to registration server
1163         if (not $done) {
1164             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1165             $res = $foreign_clients_db->select_dbentry($sql);
1166             if (keys(%$res) > 0) {
1167                 my $hostname = $res->{1}->{'hostname'};
1168                 my $regserver = $res->{1}->{'regserver'};
1169                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1170                 my $res = $known_server_db->select_dbentry($sql);
1171                 if (keys(%$res) > 0) {
1172                     my $regserver_key = $res->{1}->{'hostkey'};
1173                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1174                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1175                     if ($source eq "GOSA") {
1176                         $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1177                     }
1178                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1179                 }
1180                 $done = 1;
1181                 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1182             }
1183         }
1185         # target is a server address -> forward to server
1186         if (not $done) {
1187             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1188             $res = $known_server_db->select_dbentry($sql);
1189             if (keys(%$res) > 0) {
1190                 my $hostkey = $res->{1}->{'hostkey'};
1192                 if ($source eq "GOSA") {
1193                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1194                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1196                 }
1198                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1199                 $done = 1;
1200                 print STDERR "target is a server address -> forward to server\n";
1201             }
1204         }
1206         if (not $done) {
1207             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1208         }
1209     }
1211     return;
1215 sub next_task {
1216     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1217     my $running_task = POE::Wheel::Run->new(
1218             Program => sub { process_task($session, $heap, $task) },
1219             StdioFilter => POE::Filter::Reference->new(),
1220             StdoutEvent  => "task_result",
1221             StderrEvent  => "task_debug",
1222             CloseEvent   => "task_done",
1223             );
1224     $heap->{task}->{ $running_task->ID } = $running_task;
1227 sub handle_task_result {
1228     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1229     my $client_answer = $result->{'answer'};
1230     if( $client_answer =~ s/session_id=(\d+)$// ) {
1231         my $session_id = $1;
1232         if( defined $session_id ) {
1233             my $session_reference = $kernel->ID_id_to_session($session_id);
1234             if( defined $session_reference ) {
1235                 $heap = $session_reference->get_heap();
1236             }
1237         }
1239         if(exists $heap->{'client'}) {
1240             $heap->{'client'}->put($client_answer);
1241         }
1242     }
1243     $kernel->sig(CHLD => "child_reap");
1246 sub handle_task_debug {
1247     my $result = $_[ARG0];
1248     print STDERR "$result\n";
1251 sub handle_task_done {
1252     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1253     delete $heap->{task}->{$task_id};
1256 sub process_task {
1257     no strict "refs";
1258     my ($session, $heap, $task) = @_;
1259     my $error = 0;
1260     my $answer_l;
1261     my ($answer_header, @answer_target_l, $answer_source);
1262     my $client_answer = "";
1264     # prepare all variables needed to process message
1265     my $msg = $task->{'xmlmessage'};
1266     my $incoming_id = $task->{'id'};
1267     my $module = $task->{'module'};
1268     my $header =  $task->{'headertag'};
1269     my $session_id = $task->{'sessionid'};
1270     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1271     my $source = @{$msg_hash->{'source'}}[0];
1272     
1273     # set timestamp of incoming client uptodate, so client will not 
1274     # be deleted from known_clients because of expiration
1275     my $act_time = &get_time();
1276     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1277     my $res = $known_clients_db->exec_statement($sql);
1279     ######################
1280     # process incoming msg
1281     if( $error == 0) {
1282         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1283         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1284         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1286         if ( 0 < @{$answer_l} ) {
1287             my $answer_str = join("\n", @{$answer_l});
1288             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1289                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1290             }
1291             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1292         } else {
1293             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1294         }
1296     }
1297     if( !$answer_l ) { $error++ };
1299     ########
1300     # answer
1301     if( $error == 0 ) {
1303         foreach my $answer ( @{$answer_l} ) {
1304             # check outgoing msg to xml validity
1305             my $answer_hash = &check_outgoing_xml_validity($answer);
1306             if( not defined $answer_hash ) { next; }
1307             
1308             $answer_header = @{$answer_hash->{'header'}}[0];
1309             @answer_target_l = @{$answer_hash->{'target'}};
1310             $answer_source = @{$answer_hash->{'source'}}[0];
1312             # deliver msg to all targets 
1313             foreach my $answer_target ( @answer_target_l ) {
1315                 # targets of msg are all gosa-si-clients in known_clients_db
1316                 if( $answer_target eq "*" ) {
1317                     # answer is for all clients
1318                     my $sql_statement= "SELECT * FROM known_clients";
1319                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1320                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1321                         my $host_name = $hit->{hostname};
1322                         my $host_key = $hit->{hostkey};
1323                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1324                         &update_jobdb_status_for_send_msgs($answer, $error);
1325                     }
1326                 }
1328                 # targets of msg are all gosa-si-server in known_server_db
1329                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1330                     # answer is for all server in known_server
1331                     my $sql_statement= "SELECT * FROM $known_server_tn";
1332                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1333                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1334                         my $host_name = $hit->{hostname};
1335                         my $host_key = $hit->{hostkey};
1336                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1337                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1338                         &update_jobdb_status_for_send_msgs($answer, $error);
1339                     }
1340                 }
1342                 # target of msg is GOsa
1343                                 elsif( $answer_target eq "GOSA" ) {
1344                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1345                                         my $add_on = "";
1346                     if( defined $session_id ) {
1347                         $add_on = ".session_id=$session_id";
1348                     }
1349                     # answer is for GOSA and has to returned to connected client
1350                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1351                     $client_answer = $gosa_answer.$add_on;
1352                 }
1354                 # target of msg is job queue at this host
1355                 elsif( $answer_target eq "JOBDB") {
1356                     $answer =~ /<header>(\S+)<\/header>/;   
1357                     my $header;
1358                     if( defined $1 ) { $header = $1; }
1359                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1360                     &update_jobdb_status_for_send_msgs($answer, $error);
1361                 }
1363                 # target of msg is a mac address
1364                 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 ) {
1365                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1366                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1367                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1368                     my $found_ip_flag = 0;
1369                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1370                         my $host_name = $hit->{hostname};
1371                         my $host_key = $hit->{hostkey};
1372                         $answer =~ s/$answer_target/$host_name/g;
1373                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1374                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1375                         &update_jobdb_status_for_send_msgs($answer, $error);
1376                         $found_ip_flag++ ;
1377                     }   
1378                     if( $found_ip_flag == 0) {
1379                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1380                         if( $bus_activ eq "true" ) { 
1381                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1382                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1383                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1384                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1385                                 my $bus_address = $hit->{hostname};
1386                                 my $bus_key = $hit->{hostkey};
1387                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1388                                 &update_jobdb_status_for_send_msgs($answer, $error);
1389                                 last;
1390                             }
1391                         }
1393                     }
1395                 #  answer is for one specific host   
1396                 } else {
1397                     # get encrypt_key
1398                     my $encrypt_key = &get_encrypt_key($answer_target);
1399                     if( not defined $encrypt_key ) {
1400                         # unknown target, forward msg to bus
1401                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1402                         if( $bus_activ eq "true" ) { 
1403                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1404                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1405                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1406                             my $res_length = keys( %{$query_res} );
1407                             if( $res_length == 0 ){
1408                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1409                                         "no bus found in known_server", 3);
1410                             }
1411                             else {
1412                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1413                                     my $bus_key = $hit->{hostkey};
1414                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1415                                     &update_jobdb_status_for_send_msgs($answer, $error);
1416                                 }
1417                             }
1418                         }
1419                         next;
1420                     }
1421                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1422                     &update_jobdb_status_for_send_msgs($answer, $error);
1423                 }
1424             }
1425         }
1426     }
1428     my $filter = POE::Filter::Reference->new();
1429     my %result = ( 
1430             status => "seems ok to me",
1431             answer => $client_answer,
1432             );
1434     my $output = $filter->put( [ \%result ] );
1435     print @$output;
1440 sub session_start {
1441     my ($kernel) = $_[KERNEL];
1442     &trigger_db_loop($kernel);
1443     $global_kernel = $kernel;
1444     $kernel->yield('register_at_foreign_servers');
1445         $kernel->yield('create_fai_server_db', $fai_server_tn );
1446         $kernel->yield('create_fai_release_db', $fai_release_tn );
1447     $kernel->yield('watch_for_next_tasks');
1448         $kernel->sig(USR1 => "sig_handler");
1449         $kernel->sig(USR2 => "create_packages_list_db");
1450         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1451         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1452         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1453     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1454         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1455     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1459 sub trigger_db_loop {
1460         my ($kernel) = @_ ;
1461 #       $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1462 #       $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1463 #       $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1464 #    $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1465 #       $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1466 #    $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1470 sub watch_for_done_jobs {
1471     my ($kernel,$heap) = @_[KERNEL, HEAP];
1473     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1474         my $res = $job_db->select_dbentry( $sql_statement );
1476     while( my ($id, $hit) = each %{$res} ) {
1477         my $jobdb_id = $hit->{id};
1478         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1479         my $res = $job_db->del_dbentry($sql_statement); 
1480     }
1482     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1486 sub watch_for_new_jobs {
1487         if($watch_for_new_jobs_in_progress == 0) {
1488                 $watch_for_new_jobs_in_progress = 1;
1489                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1491                 # check gosa job queue for jobs with executable timestamp
1492                 my $timestamp = &get_time();
1493                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1494                 my $res = $job_db->exec_statement( $sql_statement );
1496                 # Merge all new jobs that would do the same actions
1497                 my @drops;
1498                 my $hits;
1499                 foreach my $hit (reverse @{$res} ) {
1500                         my $macaddress= lc @{$hit}[8];
1501                         my $headertag= @{$hit}[5];
1502                         if(
1503                                 defined($hits->{$macaddress}) &&
1504                                 defined($hits->{$macaddress}->{$headertag}) &&
1505                                 defined($hits->{$macaddress}->{$headertag}[0])
1506                         ) {
1507                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1508                         }
1509                         $hits->{$macaddress}->{$headertag}= $hit;
1510                 }
1512                 # Delete new jobs with a matching job in state 'processing'
1513                 foreach my $macaddress (keys %{$hits}) {
1514                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1515                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1516                                 if(defined($jobdb_id)) {
1517                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1518                                         my $res = $job_db->exec_statement( $sql_statement );
1519                                         foreach my $hit (@{$res}) {
1520                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1521                                         }
1522                                 } else {
1523                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1524                                 }
1525                         }
1526                 }
1528                 # Commit deletion
1529                 $job_db->exec_statementlist(\@drops);
1531                 # Look for new jobs that could be executed
1532                 foreach my $macaddress (keys %{$hits}) {
1534                         # Look if there is an executing job
1535                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1536                         my $res = $job_db->exec_statement( $sql_statement );
1538                         # Skip new jobs for host if there is a processing job
1539                         if(defined($res) and defined @{$res}[0]) {
1540                                 next;
1541                         }
1543                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1544                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1545                                 if(defined($jobdb_id)) {
1546                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1548                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1549                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1550                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1552                                         # expect macaddress is unique!!!!!!
1553                                         my $target = $res_hash->{1}->{hostname};
1555                                         # change header
1556                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1558                                         # add sqlite_id
1559                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1561                                         $job_msg =~ /<header>(\S+)<\/header>/;
1562                                         my $header = $1 ;
1563                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1565                                         # update status in job queue to 'processing'
1566                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1567                                         my $res = $job_db->update_dbentry($sql_statement);
1568 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1570                                         # We don't want parallel processing
1571                                         last;
1572                                 }
1573                         }
1574                 }
1576                 $watch_for_new_jobs_in_progress = 0;
1577                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1578         }
1582 sub watch_for_new_messages {
1583     my ($kernel,$heap) = @_[KERNEL, HEAP];
1584     my @coll_user_msg;   # collection list of outgoing messages
1585     
1586     # check messaging_db for new incoming messages with executable timestamp
1587     my $timestamp = &get_time();
1588     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1589     my $res = $messaging_db->exec_statement( $sql_statement );
1590         foreach my $hit (@{$res}) {
1592         # create outgoing messages
1593         my $message_to = @{$hit}[3];
1594         # translate message_to to plain login name
1595         my @message_to_l = split(/,/, $message_to);  
1596                 my %receiver_h; 
1597                 foreach my $receiver (@message_to_l) {
1598                         if ($receiver =~ /^u_([\s\S]*)$/) {
1599                                 $receiver_h{$1} = 0;
1600                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1601                                 my $group_name = $1;
1602                                 # fetch all group members from ldap and add them to receiver hash
1603                                 my $ldap_handle = &get_ldap_handle();
1604                                 if (defined $ldap_handle) {
1605                                                 my $mesg = $ldap_handle->search(
1606                                                                                 base => $ldap_base,
1607                                                                                 scope => 'sub',
1608                                                                                 attrs => ['memberUid'],
1609                                                                                 filter => "cn=$group_name",
1610                                                                                 );
1611                                                 if ($mesg->count) {
1612                                                                 my @entries = $mesg->entries;
1613                                                                 foreach my $entry (@entries) {
1614                                                                                 my @receivers= $entry->get_value("memberUid");
1615                                                                                 foreach my $receiver (@receivers) { 
1616                                                                                                 $receiver_h{$1} = 0;
1617                                                                                 }
1618                                                                 }
1619                                                 } 
1620                                                 # translating errors ?
1621                                                 if ($mesg->code) {
1622                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1623                                                 }
1624                                 # ldap handle error ?           
1625                                 } else {
1626                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1627                                 }
1628                         } else {
1629                                 my $sbjct = &encode_base64(@{$hit}[1]);
1630                                 my $msg = &encode_base64(@{$hit}[7]);
1631                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1632                         }
1633                 }
1634                 my @receiver_l = keys(%receiver_h);
1636         my $message_id = @{$hit}[0];
1638         #add each outgoing msg to messaging_db
1639         my $receiver;
1640         foreach $receiver (@receiver_l) {
1641             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1642                 "VALUES ('".
1643                 $message_id."', '".    # id
1644                 @{$hit}[1]."', '".     # subject
1645                 @{$hit}[2]."', '".     # message_from
1646                 $receiver."', '".      # message_to
1647                 "none"."', '".         # flag
1648                 "out"."', '".          # direction
1649                 @{$hit}[6]."', '".     # delivery_time
1650                 @{$hit}[7]."', '".     # message
1651                 $timestamp."'".     # timestamp
1652                 ")";
1653             &daemon_log("M DEBUG: $sql_statement", 1);
1654             my $res = $messaging_db->exec_statement($sql_statement);
1655             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1656         }
1658         # set incoming message to flag d=deliverd
1659         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1660         &daemon_log("M DEBUG: $sql_statement", 7);
1661         $res = $messaging_db->update_dbentry($sql_statement);
1662         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1663     }
1665     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1666     return;
1669 sub watch_for_delivery_messages {
1670     my ($kernel, $heap) = @_[KERNEL, HEAP];
1672     # select outgoing messages
1673     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1674     #&daemon_log("0 DEBUG: $sql", 7);
1675     my $res = $messaging_db->exec_statement( $sql_statement );
1676     
1677     # build out msg for each    usr
1678     foreach my $hit (@{$res}) {
1679         my $receiver = @{$hit}[3];
1680         my $msg_id = @{$hit}[0];
1681         my $subject = @{$hit}[1];
1682         my $message = @{$hit}[7];
1684         # resolve usr -> host where usr is logged in
1685         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1686         #&daemon_log("0 DEBUG: $sql", 7);
1687         my $res = $login_users_db->exec_statement($sql);
1689         # reciver is logged in nowhere
1690         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1692                 my $send_succeed = 0;
1693                 foreach my $hit (@$res) {
1694                                 my $receiver_host = @$hit[0];
1695                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1697                                 # fetch key to encrypt msg propperly for usr/host
1698                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1699                                 &daemon_log("0 DEBUG: $sql", 7);
1700                                 my $res = $known_clients_db->select_dbentry($sql);
1702                                 # host is already down
1703                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1705                                 # host is on
1706                                 my $receiver_key = @{@{$res}[0]}[2];
1707                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1708                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1709                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1710                                 if ($error == 0 ) {
1711                                         $send_succeed++ ;
1712                                 }
1713                 }
1715                 if ($send_succeed) {
1716                                 # set outgoing msg at db to deliverd
1717                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1718                                 &daemon_log("0 DEBUG: $sql", 7);
1719                                 my $res = $messaging_db->exec_statement($sql); 
1720                 }
1721         }
1723     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1724     return;
1728 sub watch_for_done_messages {
1729     my ($kernel,$heap) = @_[KERNEL, HEAP];
1731     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1732     #&daemon_log("0 DEBUG: $sql", 7);
1733     my $res = $messaging_db->exec_statement($sql); 
1735     foreach my $hit (@{$res}) {
1736         my $msg_id = @{$hit}[0];
1738         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1739         #&daemon_log("0 DEBUG: $sql", 7); 
1740         my $res = $messaging_db->exec_statement($sql);
1742         # not all usr msgs have been seen till now
1743         if ( ref(@$res[0]) eq "ARRAY") { next; }
1744         
1745         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1746         #&daemon_log("0 DEBUG: $sql", 7);
1747         $res = $messaging_db->exec_statement($sql);
1748     
1749     }
1751     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1752     return;
1756 sub watch_for_old_known_clients {
1757     my ($kernel,$heap) = @_[KERNEL, HEAP];
1759     my $sql_statement = "SELECT * FROM $known_clients_tn";
1760     my $res = $known_clients_db->select_dbentry( $sql_statement );
1762     my $act_time = int(&get_time());
1764     while ( my ($hit_num, $hit) = each %$res) {
1765         my $expired_timestamp = int($hit->{'timestamp'});
1766         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1767         my $dt = DateTime->new( year   => $1,
1768                 month  => $2,
1769                 day    => $3,
1770                 hour   => $4,
1771                 minute => $5,
1772                 second => $6,
1773                 );
1775         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1776         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1777         if ($act_time > $expired_timestamp) {
1778             my $hostname = $hit->{'hostname'};
1779             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1780             my $del_res = $known_clients_db->exec_statement($del_sql);
1782             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1783         }
1785     }
1787     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1791 sub watch_for_next_tasks {
1792     my ($kernel,$heap) = @_[KERNEL, HEAP];
1794     my $sql = "SELECT * FROM $incoming_tn";
1795     my $res = $incoming_db->select_dbentry($sql);
1797     while ( my ($hit_num, $hit) = each %$res) {
1798         my $headertag = $hit->{'headertag'};
1799         if ($headertag =~ /^answer_(\d+)/) {
1800             # do not start processing, this message is for a still running POE::Wheel
1801             next;
1802         }
1803         my $message_id = $hit->{'id'};
1804         $kernel->yield('next_task', $hit);
1806         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1807         my $res = $incoming_db->exec_statement($sql);
1808     }
1810     $kernel->delay_set('watch_for_next_tasks', 1); 
1814 sub get_ldap_handle {
1815         my ($session_id) = @_;
1816         my $heap;
1817         my $ldap_handle;
1819         if (not defined $session_id ) { $session_id = 0 };
1820         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1822         if ($session_id == 0) {
1823                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1824                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1825                 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password); 
1827         } else {
1828                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1829                 if( defined $session_reference ) {
1830                         $heap = $session_reference->get_heap();
1831                 }
1833                 if (not defined $heap) {
1834                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1835                         return;
1836                 }
1838                 # TODO: This "if" is nonsense, because it doesn't prove that the
1839                 #       used handle is still valid - or if we've to reconnect...
1840                 #if (not exists $heap->{ldap_handle}) {
1841                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1842                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1843                         $heap->{ldap_handle} = $ldap_handle;
1844                 #}
1845         }
1846         return $ldap_handle;
1850 sub change_fai_state {
1851     my ($st, $targets, $session_id) = @_;
1852     $session_id = 0 if not defined $session_id;
1853     # Set FAI state to localboot
1854     my %mapActions= (
1855         reboot    => '',
1856         update    => 'softupdate',
1857         localboot => 'localboot',
1858         reinstall => 'install',
1859         rescan    => '',
1860         wake      => '',
1861         memcheck  => 'memcheck',
1862         sysinfo   => 'sysinfo',
1863         install   => 'install',
1864     );
1866     # Return if this is unknown
1867     if (!exists $mapActions{ $st }){
1868         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1869       return;
1870     }
1872     my $state= $mapActions{ $st };
1874     my $ldap_handle = &get_ldap_handle($session_id);
1875     if( defined($ldap_handle) ) {
1877       # Build search filter for hosts
1878         my $search= "(&(objectClass=GOhard)";
1879         foreach (@{$targets}){
1880             $search.= "(macAddress=$_)";
1881         }
1882         $search.= ")";
1884       # If there's any host inside of the search string, procress them
1885         if (!($search =~ /macAddress/)){
1886             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1887             return;
1888         }
1890       # Perform search for Unit Tag
1891       my $mesg = $ldap_handle->search(
1892           base   => $ldap_base,
1893           scope  => 'sub',
1894           attrs  => ['dn', 'FAIstate', 'objectClass'],
1895           filter => "$search"
1896           );
1898           if ($mesg->count) {
1899                   my @entries = $mesg->entries;
1900                   foreach my $entry (@entries) {
1901                           # Only modify entry if it is not set to '$state'
1902                           if ($entry->get_value("FAIstate") ne "$state"){
1903                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1904                                   my $result;
1905                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1906                                   if (exists $tmp{'FAIobject'}){
1907                                           if ($state eq ''){
1908                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1909                                                           delete => [ FAIstate => [] ] ]);
1910                                           } else {
1911                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1912                                                           replace => [ FAIstate => $state ] ]);
1913                                           }
1914                                   } elsif ($state ne ''){
1915                                           $result= $ldap_handle->modify($entry->dn, changes => [
1916                                                   add     => [ objectClass => 'FAIobject' ],
1917                                                   add     => [ FAIstate => $state ] ]);
1918                                   }
1920                                   # Errors?
1921                                   if ($result->code){
1922                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1923                                   }
1924                           } else {
1925                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1926                           }  
1927                   }
1928           }
1929     # if no ldap handle defined
1930     } else {
1931         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1932     }
1937 sub change_goto_state {
1938     my ($st, $targets, $session_id) = @_;
1939     $session_id = 0  if not defined $session_id;
1941     # Switch on or off?
1942     my $state= $st eq 'active' ? 'active': 'locked';
1944     my $ldap_handle = &get_ldap_handle($session_id);
1945     if( defined($ldap_handle) ) {
1947       # Build search filter for hosts
1948       my $search= "(&(objectClass=GOhard)";
1949       foreach (@{$targets}){
1950         $search.= "(macAddress=$_)";
1951       }
1952       $search.= ")";
1954       # If there's any host inside of the search string, procress them
1955       if (!($search =~ /macAddress/)){
1956         return;
1957       }
1959       # Perform search for Unit Tag
1960       my $mesg = $ldap_handle->search(
1961           base   => $ldap_base,
1962           scope  => 'sub',
1963           attrs  => ['dn', 'gotoMode'],
1964           filter => "$search"
1965           );
1967       if ($mesg->count) {
1968         my @entries = $mesg->entries;
1969         foreach my $entry (@entries) {
1971           # Only modify entry if it is not set to '$state'
1972           if ($entry->get_value("gotoMode") ne $state){
1974             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1975             my $result;
1976             $result= $ldap_handle->modify($entry->dn, changes => [
1977                                                 replace => [ gotoMode => $state ] ]);
1979             # Errors?
1980             if ($result->code){
1981               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1982             }
1984           }
1985         }
1986       }
1988     }
1992 sub run_create_fai_server_db {
1993     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1994     my $session_id = $session->ID;
1995     my $task = POE::Wheel::Run->new(
1996             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1997             StdoutEvent  => "session_run_result",
1998             StderrEvent  => "session_run_debug",
1999             CloseEvent   => "session_run_done",
2000             );
2002     $heap->{task}->{ $task->ID } = $task;
2003     return;
2007 sub create_fai_server_db {
2008     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2009         my $result;
2011         if (not defined $session_id) { $session_id = 0; }
2012     my $ldap_handle = &get_ldap_handle();
2013         if(defined($ldap_handle)) {
2014                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2015                 my $mesg= $ldap_handle->search(
2016                         base   => $ldap_base,
2017                         scope  => 'sub',
2018                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2019                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2020                 );
2021                 if($mesg->{'resultCode'} == 0 &&
2022                    $mesg->count != 0) {
2023                    foreach my $entry (@{$mesg->{entries}}) {
2024                            if($entry->exists('FAIrepository')) {
2025                                    # Add an entry for each Repository configured for server
2026                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2027                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2028                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2029                                                    $result= $fai_server_db->add_dbentry( { 
2030                                                                    table => $table_name,
2031                                                                    primkey => ['server', 'release', 'tag'],
2032                                                                    server => $tmp_url,
2033                                                                    release => $tmp_release,
2034                                                                    sections => $tmp_sections,
2035                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2036                                                            } );
2037                                            }
2038                                    }
2039                            }
2040                    }
2041                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2043                 # TODO: Find a way to post the 'create_packages_list_db' event
2044                 if(not defined($dont_create_packages_list)) {
2045                         &create_packages_list_db(undef, undef, $session_id);
2046                 }
2047         }       
2048     
2049     $ldap_handle->disconnect;
2050         return $result;
2054 sub run_create_fai_release_db {
2055     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2056         my $session_id = $session->ID;
2057     my $task = POE::Wheel::Run->new(
2058             Program => sub { &create_fai_release_db($table_name, $session_id) },
2059             StdoutEvent  => "session_run_result",
2060             StderrEvent  => "session_run_debug",
2061             CloseEvent   => "session_run_done",
2062             );
2064     $heap->{task}->{ $task->ID } = $task;
2065     return;
2069 sub create_fai_release_db {
2070         my ($table_name, $session_id) = @_;
2071         my $result;
2073     # used for logging
2074     if (not defined $session_id) { $session_id = 0; }
2076     my $ldap_handle = &get_ldap_handle();
2077         if(defined($ldap_handle)) {
2078                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2079                 my $mesg= $ldap_handle->search(
2080                         base   => $ldap_base,
2081                         scope  => 'sub',
2082                         attrs  => [],
2083                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2084                 );
2085                 if($mesg->{'resultCode'} == 0 &&
2086                         $mesg->count != 0) {
2087                         # Walk through all possible FAI container ou's
2088                         my @sql_list;
2089                         my $timestamp= &get_time();
2090                         foreach my $ou (@{$mesg->{entries}}) {
2091                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2092                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2093                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2094                                         if(@tmp_array) {
2095                                                 foreach my $entry (@tmp_array) {
2096                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2097                                                                 my $sql= 
2098                                                                 "INSERT INTO $table_name "
2099                                                                 ."(timestamp, release, class, type, state) VALUES ("
2100                                                                 .$timestamp.","
2101                                                                 ."'".$entry->{'release'}."',"
2102                                                                 ."'".$entry->{'class'}."',"
2103                                                                 ."'".$entry->{'type'}."',"
2104                                                                 ."'".$entry->{'state'}."')";
2105                                                                 push @sql_list, $sql;
2106                                                         }
2107                                                 }
2108                                         }
2109                                 }
2110                         }
2112                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2113                         if(@sql_list) {
2114                                 unshift @sql_list, "VACUUM";
2115                                 unshift @sql_list, "DELETE FROM $table_name";
2116                                 $fai_release_db->exec_statementlist(\@sql_list);
2117                         }
2118                         daemon_log("$session_id DEBUG: Done with inserting",7);
2119                 }
2120                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2121         }
2122     $ldap_handle->disconnect;
2123         return $result;
2126 sub get_fai_types {
2127         my $tmp_classes = shift || return undef;
2128         my @result;
2130         foreach my $type(keys %{$tmp_classes}) {
2131                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2132                         my $entry = {
2133                                 type => $type,
2134                                 state => $tmp_classes->{$type}[0],
2135                         };
2136                         push @result, $entry;
2137                 }
2138         }
2140         return @result;
2143 sub get_fai_state {
2144         my $result = "";
2145         my $tmp_classes = shift || return $result;
2147         foreach my $type(keys %{$tmp_classes}) {
2148                 if(defined($tmp_classes->{$type}[0])) {
2149                         $result = $tmp_classes->{$type}[0];
2150                         
2151                 # State is equal for all types in class
2152                         last;
2153                 }
2154         }
2156         return $result;
2159 sub resolve_fai_classes {
2160         my ($fai_base, $ldap_handle, $session_id) = @_;
2161         if (not defined $session_id) { $session_id = 0; }
2162         my $result;
2163         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2164         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2165         my $fai_classes;
2167         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2168         my $mesg= $ldap_handle->search(
2169                 base   => $fai_base,
2170                 scope  => 'sub',
2171                 attrs  => ['cn','objectClass','FAIstate'],
2172                 filter => $fai_filter,
2173         );
2174         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2176         if($mesg->{'resultCode'} == 0 &&
2177                 $mesg->count != 0) {
2178                 foreach my $entry (@{$mesg->{entries}}) {
2179                         if($entry->exists('cn')) {
2180                                 my $tmp_dn= $entry->dn();
2182                                 # Skip classname and ou dn parts for class
2183                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2185                                 # Skip classes without releases
2186                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2187                                         next;
2188                                 }
2190                                 my $tmp_cn= $entry->get_value('cn');
2191                                 my $tmp_state= $entry->get_value('FAIstate');
2193                                 my $tmp_type;
2194                                 # Get FAI type
2195                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2196                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2197                                                 $tmp_type= $oclass;
2198                                                 last;
2199                                         }
2200                                 }
2202                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2203                                         # A Subrelease
2204                                         my @sub_releases = split(/,/, $tmp_release);
2206                                         # Walk through subreleases and build hash tree
2207                                         my $hash;
2208                                         while(my $tmp_sub_release = pop @sub_releases) {
2209                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2210                                         }
2211                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2212                                 } else {
2213                                         # A branch, no subrelease
2214                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2215                                 }
2216                         } elsif (!$entry->exists('cn')) {
2217                                 my $tmp_dn= $entry->dn();
2218                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2220                                 # Skip classes without releases
2221                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2222                                         next;
2223                                 }
2225                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2226                                         # A Subrelease
2227                                         my @sub_releases= split(/,/, $tmp_release);
2229                                         # Walk through subreleases and build hash tree
2230                                         my $hash;
2231                                         while(my $tmp_sub_release = pop @sub_releases) {
2232                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2233                                         }
2234                                         # Remove the last two characters
2235                                         chop($hash);
2236                                         chop($hash);
2238                                         eval('$fai_classes->'.$hash.'= {}');
2239                                 } else {
2240                                         # A branch, no subrelease
2241                                         if(!exists($fai_classes->{$tmp_release})) {
2242                                                 $fai_classes->{$tmp_release} = {};
2243                                         }
2244                                 }
2245                         }
2246                 }
2248                 # The hash is complete, now we can honor the copy-on-write based missing entries
2249                 foreach my $release (keys %$fai_classes) {
2250                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2251                 }
2252         }
2253         return $result;
2256 sub apply_fai_inheritance {
2257        my $fai_classes = shift || return {};
2258        my $tmp_classes;
2260        # Get the classes from the branch
2261        foreach my $class (keys %{$fai_classes}) {
2262                # Skip subreleases
2263                if($class =~ /^ou=.*$/) {
2264                        next;
2265                } else {
2266                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2267                }
2268        }
2270        # Apply to each subrelease
2271        foreach my $subrelease (keys %{$fai_classes}) {
2272                if($subrelease =~ /ou=/) {
2273                        foreach my $tmp_class (keys %{$tmp_classes}) {
2274                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2275                                        $fai_classes->{$subrelease}->{$tmp_class} =
2276                                        deep_copy($tmp_classes->{$tmp_class});
2277                                } else {
2278                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2279                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2280                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2281                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2282                                                }
2283                                        }
2284                                }
2285                        }
2286                }
2287        }
2289        # Find subreleases in deeper levels
2290        foreach my $subrelease (keys %{$fai_classes}) {
2291                if($subrelease =~ /ou=/) {
2292                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2293                                if($subsubrelease =~ /ou=/) {
2294                                        apply_fai_inheritance($fai_classes->{$subrelease});
2295                                }
2296                        }
2297                }
2298        }
2300        return $fai_classes;
2303 sub get_fai_release_entries {
2304         my $tmp_classes = shift || return;
2305         my $parent = shift || "";
2306         my @result = shift || ();
2308         foreach my $entry (keys %{$tmp_classes}) {
2309                 if(defined($entry)) {
2310                         if($entry =~ /^ou=.*$/) {
2311                                 my $release_name = $entry;
2312                                 $release_name =~ s/ou=//g;
2313                                 if(length($parent)>0) {
2314                                         $release_name = $parent."/".$release_name;
2315                                 }
2316                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2317                                 foreach my $bufentry(@bufentries) {
2318                                         push @result, $bufentry;
2319                                 }
2320                         } else {
2321                                 my @types = get_fai_types($tmp_classes->{$entry});
2322                                 foreach my $type (@types) {
2323                                         push @result, 
2324                                         {
2325                                                 'class' => $entry,
2326                                                 'type' => $type->{'type'},
2327                                                 'release' => $parent,
2328                                                 'state' => $type->{'state'},
2329                                         };
2330                                 }
2331                         }
2332                 }
2333         }
2335         return @result;
2338 sub deep_copy {
2339         my $this = shift;
2340         if (not ref $this) {
2341                 $this;
2342         } elsif (ref $this eq "ARRAY") {
2343                 [map deep_copy($_), @$this];
2344         } elsif (ref $this eq "HASH") {
2345                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2346         } else { die "what type is $_?" }
2350 sub session_run_result {
2351     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2352     $kernel->sig(CHLD => "child_reap");
2355 sub session_run_debug {
2356     my $result = $_[ARG0];
2357     print STDERR "$result\n";
2360 sub session_run_done {
2361     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2362     delete $heap->{task}->{$task_id};
2366 sub create_sources_list {
2367         my $session_id = shift;
2368         my $ldap_handle = &main::get_ldap_handle;
2369         my $result="/tmp/gosa_si_tmp_sources_list";
2371         # Remove old file
2372         if(stat($result)) {
2373                 unlink($result);
2374                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2375         }
2377         my $fh;
2378         open($fh, ">$result");
2379         if (not defined $fh) {
2380                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2381                 return undef;
2382         }
2383         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2384                 my $mesg=$ldap_handle->search(
2385                         base    => $main::ldap_server_dn,
2386                         scope   => 'base',
2387                         attrs   => 'FAIrepository',
2388                         filter  => 'objectClass=FAIrepositoryServer'
2389                 );
2390                 if($mesg->count) {
2391                         foreach my $entry(@{$mesg->{'entries'}}) {
2392                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2393                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2394                                         my $line = "deb $server $release";
2395                                         $sections =~ s/,/ /g;
2396                                         $line.= " $sections";
2397                                         print $fh $line."\n";
2398                                 }
2399                         }
2400                 }
2401         } else {
2402                 if (defined $main::ldap_server_dn){
2403                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2404                 } else {
2405                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2406                 }
2407         }
2408         close($fh);
2410         return $result;
2414 sub run_create_packages_list_db {
2415     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2416         my $session_id = $session->ID;
2418         my $task = POE::Wheel::Run->new(
2419                                         Priority => +20,
2420                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2421                                         StdoutEvent  => "session_run_result",
2422                                         StderrEvent  => "session_run_debug",
2423                                         CloseEvent   => "session_run_done",
2424                                         );
2425         $heap->{task}->{ $task->ID } = $task;
2429 sub create_packages_list_db {
2430         my ($ldap_handle, $sources_file, $session_id) = @_;
2431         
2432         # it should not be possible to trigger a recreation of packages_list_db
2433         # while packages_list_db is under construction, so set flag packages_list_under_construction
2434         # which is tested befor recreation can be started
2435         if (-r $packages_list_under_construction) {
2436                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2437                 return;
2438         } else {
2439                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2440                 # set packages_list_under_construction to true
2441                 system("touch $packages_list_under_construction");
2442                 @packages_list_statements=();
2443         }
2445         if (not defined $session_id) { $session_id = 0; }
2446         if (not defined $ldap_handle) { 
2447                 $ldap_handle= &get_ldap_handle();
2449                 if (not defined $ldap_handle) {
2450                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2451                         unlink($packages_list_under_construction);
2452                         return;
2453                 }
2454         }
2455         if (not defined $sources_file) { 
2456                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2457                 $sources_file = &create_sources_list($session_id);
2458         }
2460         if (not defined $sources_file) {
2461                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2462                 unlink($packages_list_under_construction);
2463                 return;
2464         }
2466         my $line;
2468         open(CONFIG, "<$sources_file") or do {
2469                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2470                 unlink($packages_list_under_construction);
2471                 return;
2472         };
2474         # Read lines
2475         while ($line = <CONFIG>){
2476                 # Unify
2477                 chop($line);
2478                 $line =~ s/^\s+//;
2479                 $line =~ s/^\s+/ /;
2481                 # Strip comments
2482                 $line =~ s/#.*$//g;
2484                 # Skip empty lines
2485                 if ($line =~ /^\s*$/){
2486                         next;
2487                 }
2489                 # Interpret deb line
2490                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2491                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2492                         my $section;
2493                         foreach $section (split(' ', $sections)){
2494                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2495                         }
2496                 }
2497         }
2499         close (CONFIG);
2501         find(\&cleanup_and_extract, keys( %repo_dirs ));
2502         &main::strip_packages_list_statements();
2503         unshift @packages_list_statements, "VACUUM";
2504         $packages_list_db->exec_statementlist(\@packages_list_statements);
2505         unlink($packages_list_under_construction);
2506         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2507         return;
2510 # This function should do some intensive task to minimize the db-traffic
2511 sub strip_packages_list_statements {
2512     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2513         my @new_statement_list=();
2514         my $hash;
2515         my $insert_hash;
2516         my $update_hash;
2517         my $delete_hash;
2518         my $local_timestamp=get_time();
2520         foreach my $existing_entry (@existing_entries) {
2521                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2522         }
2524         foreach my $statement (@packages_list_statements) {
2525                 if($statement =~ /^INSERT/i) {
2526                         # Assign the values from the insert statement
2527                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2528                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2529                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2530                                 # If section or description has changed, update the DB
2531                                 if( 
2532                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2533                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2534                                 ) {
2535                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2536                                 }
2537                         } else {
2538                                 # Insert a non-existing entry to db
2539                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2540                         }
2541                 } elsif ($statement =~ /^UPDATE/i) {
2542                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2543                         /^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;
2544                         foreach my $distribution (keys %{$hash}) {
2545                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2546                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2547                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2548                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2549                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2550                                                 my $section;
2551                                                 my $description;
2552                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2553                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2554                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2555                                                 }
2556                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2557                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2558                                                 }
2559                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2560                                         }
2561                                 }
2562                         }
2563                 }
2564         }
2566         # TODO: Check for orphaned entries
2568         # unroll the insert_hash
2569         foreach my $distribution (keys %{$insert_hash}) {
2570                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2571                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2572                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2573                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2574                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2575                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2576                                 ."'$local_timestamp')";
2577                         }
2578                 }
2579         }
2581         # unroll the update hash
2582         foreach my $distribution (keys %{$update_hash}) {
2583                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2584                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2585                                 my $set = "";
2586                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2587                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2588                                 }
2589                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2590                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2591                                 }
2592                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2593                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2594                                 }
2595                                 if(defined($set) and length($set) > 0) {
2596                                         $set .= "timestamp = '$local_timestamp'";
2597                                 } else {
2598                                         next;
2599                                 }
2600                                 push @new_statement_list, 
2601                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2602                                         ." distribution = '$distribution'"
2603                                         ." AND package = '$package'"
2604                                         ." AND version = '$version'";
2605                         }
2606                 }
2607         }
2609         @packages_list_statements = @new_statement_list;
2613 sub parse_package_info {
2614     my ($baseurl, $dist, $section, $session_id)= @_;
2615     my ($package);
2616     if (not defined $session_id) { $session_id = 0; }
2617     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2618     $repo_dirs{ "${repo_path}/pool" } = 1;
2620     foreach $package ("Packages.gz"){
2621         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2622         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2623         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2624     }
2625     
2629 sub get_package {
2630     my ($url, $dest, $session_id)= @_;
2631     if (not defined $session_id) { $session_id = 0; }
2633     my $tpath = dirname($dest);
2634     -d "$tpath" || mkpath "$tpath";
2636     # This is ugly, but I've no time to take a look at "how it works in perl"
2637     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2638         system("gunzip -cd '$dest' > '$dest.in'");
2639         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2640         unlink($dest);
2641         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2642     } else {
2643         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2644     }
2645     return 0;
2649 sub parse_package {
2650     my ($path, $dist, $srv_path, $session_id)= @_;
2651     if (not defined $session_id) { $session_id = 0;}
2652     my ($package, $version, $section, $description);
2653     my $PACKAGES;
2654     my $timestamp = &get_time();
2656     if(not stat("$path.in")) {
2657         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2658         return;
2659     }
2661     open($PACKAGES, "<$path.in");
2662     if(not defined($PACKAGES)) {
2663         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2664         return;
2665     }
2667     # Read lines
2668     while (<$PACKAGES>){
2669         my $line = $_;
2670         # Unify
2671         chop($line);
2673         # Use empty lines as a trigger
2674         if ($line =~ /^\s*$/){
2675             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2676             push(@packages_list_statements, $sql);
2677             $package = "none";
2678             $version = "none";
2679             $section = "none";
2680             $description = "none"; 
2681             next;
2682         }
2684         # Trigger for package name
2685         if ($line =~ /^Package:\s/){
2686             ($package)= ($line =~ /^Package: (.*)$/);
2687             next;
2688         }
2690         # Trigger for version
2691         if ($line =~ /^Version:\s/){
2692             ($version)= ($line =~ /^Version: (.*)$/);
2693             next;
2694         }
2696         # Trigger for description
2697         if ($line =~ /^Description:\s/){
2698             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2699             next;
2700         }
2702         # Trigger for section
2703         if ($line =~ /^Section:\s/){
2704             ($section)= ($line =~ /^Section: (.*)$/);
2705             next;
2706         }
2708         # Trigger for filename
2709         if ($line =~ /^Filename:\s/){
2710             my ($filename) = ($line =~ /^Filename: (.*)$/);
2711             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2712             next;
2713         }
2714     }
2716     close( $PACKAGES );
2717     unlink( "$path.in" );
2718     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2722 sub store_fileinfo {
2723     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2725     my %fileinfo = (
2726         'package' => $package,
2727         'dist' => $dist,
2728         'version' => $vers,
2729     );
2731     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2735 sub cleanup_and_extract {
2736     my $fileinfo = $repo_files{ $File::Find::name };
2738     if( defined $fileinfo ) {
2740         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2741         my $sql;
2742         my $package = $fileinfo->{ 'package' };
2743         my $newver = $fileinfo->{ 'version' };
2745         mkpath($dir);
2746         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2748                 if( -f "$dir/DEBIAN/templates" ) {
2750                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2752                         my $tmpl= "";
2753                         {
2754                                 local $/=undef;
2755                                 open FILE, "$dir/DEBIAN/templates";
2756                                 $tmpl = &encode_base64(<FILE>);
2757                                 close FILE;
2758                         }
2759                         rmtree("$dir/DEBIAN/templates");
2761                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2762                 push @packages_list_statements, $sql;
2763                 }
2764     }
2766     return;
2770 sub register_at_foreign_servers {   
2771     my ($kernel) = $_[KERNEL];
2773     # hole alle bekannten server aus known_server_db
2774     my $server_sql = "SELECT * FROM $known_server_tn";
2775     my $server_res = $known_server_db->exec_statement($server_sql);
2777     # no entries in known_server_db
2778     if (not ref(@$server_res[0]) eq "ARRAY") { 
2779         # TODO
2780     }
2782     # detect already connected clients
2783     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2784     my $client_res = $known_clients_db->exec_statement($client_sql);
2786     # send my server details to all other gosa-si-server within the network
2787     foreach my $hit (@$server_res) {
2788         my $hostname = @$hit[0];
2789         my $hostkey = &create_passwd;
2791         # add already connected clients to registration message 
2792         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2793         &add_content2xml_hash($myhash, 'key', $hostkey);
2794         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2795         
2796         # build registration message and send it
2797         my $foreign_server_msg = &create_xml_string($myhash);
2798         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2799     }
2800     
2801     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2802     return;
2806 #==== MAIN = main ==============================================================
2807 #  parse commandline options
2808 Getopt::Long::Configure( "bundling" );
2809 GetOptions("h|help" => \&usage,
2810         "c|config=s" => \$cfg_file,
2811         "f|foreground" => \$foreground,
2812         "v|verbose+" => \$verbose,
2813         "no-bus+" => \$no_bus,
2814         "no-arp+" => \$no_arp,
2815            );
2817 #  read and set config parameters
2818 &check_cmdline_param ;
2819 &read_configfile;
2820 &check_pid;
2822 $SIG{CHLD} = 'IGNORE';
2824 # forward error messages to logfile
2825 if( ! $foreground ) {
2826   open( STDIN,  '+>/dev/null' );
2827   open( STDOUT, '+>&STDIN'    );
2828   open( STDERR, '+>&STDIN'    );
2831 # Just fork, if we are not in foreground mode
2832 if( ! $foreground ) { 
2833     chdir '/'                 or die "Can't chdir to /: $!";
2834     $pid = fork;
2835     setsid                    or die "Can't start a new session: $!";
2836     umask 0;
2837 } else { 
2838     $pid = $$; 
2841 # Do something useful - put our PID into the pid_file
2842 if( 0 != $pid ) {
2843     open( LOCK_FILE, ">$pid_file" );
2844     print LOCK_FILE "$pid\n";
2845     close( LOCK_FILE );
2846     if( !$foreground ) { 
2847         exit( 0 ) 
2848     };
2851 # parse head url and revision from svn
2852 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2853 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2854 $server_headURL = defined $1 ? $1 : 'unknown' ;
2855 $server_revision = defined $2 ? $2 : 'unknown' ;
2856 if ($server_headURL =~ /\/tag\// || 
2857         $server_headURL =~ /\/branches\// ) {
2858     $server_status = "stable"; 
2859 } else {
2860     $server_status = "developmental" ;
2864 daemon_log(" ", 1);
2865 daemon_log("$0 started!", 1);
2866 daemon_log("status: $server_status", 1);
2867 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2869 if ($no_bus > 0) {
2870     $bus_activ = "false"
2873 # connect to incoming_db
2874 unlink($incoming_file_name);
2875 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2876 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2878 # connect to gosa-si job queue
2879 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2880 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2882 # connect to known_clients_db
2883 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2884 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2886 # connect to foreign_clients_db
2887 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2888 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2890 # connect to known_server_db
2891 unlink($known_server_file_name);
2892 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2893 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2895 # connect to login_usr_db
2896 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2897 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2899 # connect to fai_server_db and fai_release_db
2900 unlink($fai_server_file_name);
2901 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2902 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2904 unlink($fai_release_file_name);
2905 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2906 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2908 # connect to packages_list_db
2909 #unlink($packages_list_file_name);
2910 unlink($packages_list_under_construction);
2911 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2912 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2914 # connect to messaging_db
2915 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2916 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2919 # create xml object used for en/decrypting
2920 $xml = new XML::Simple();
2923 # foreign servers 
2924 my @foreign_server_list;
2926 # add foreign server from cfg file
2927 if ($foreign_server_string ne "") {
2928     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2929     foreach my $foreign_server (@cfg_foreign_server_list) {
2930         push(@foreign_server_list, $foreign_server);
2931     }
2934 # add foreign server from dns
2935 my @tmp_servers;
2936 if ( !$server_domain) {
2937     # Try our DNS Searchlist
2938     for my $domain(get_dns_domains()) {
2939         chomp($domain);
2940         my @tmp_domains= &get_server_addresses($domain);
2941         if(@tmp_domains) {
2942             for my $tmp_server(@tmp_domains) {
2943                 push @tmp_servers, $tmp_server;
2944             }
2945         }
2946     }
2947     if(@tmp_servers && length(@tmp_servers)==0) {
2948         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2949     }
2950 } else {
2951     @tmp_servers = &get_server_addresses($server_domain);
2952     if( 0 == @tmp_servers ) {
2953         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2954     }
2956 foreach my $server (@tmp_servers) { 
2957     unshift(@foreign_server_list, $server); 
2959 # eliminate duplicate entries
2960 @foreign_server_list = &del_doubles(@foreign_server_list);
2961 my $all_foreign_server = join(", ", @foreign_server_list);
2962 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2964 # add all found foreign servers to known_server
2965 my $act_timestamp = &get_time();
2966 foreach my $foreign_server (@foreign_server_list) {
2967     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2968             primkey=>['hostname'],
2969             hostname=>$foreign_server,
2970             status=>'not_jet_registered',
2971             hostkey=>"none",
2972             timestamp=>$act_timestamp,
2973             } );
2977 POE::Component::Server::TCP->new(
2978     Alias => "TCP_SERVER",
2979         Port => $server_port,
2980         ClientInput => sub {
2981         my ($kernel, $input) = @_[KERNEL, ARG0];
2982         push(@tasks, $input);
2983         push(@msgs_to_decrypt, $input);
2984         $kernel->yield("msg_to_decrypt");
2985         },
2986     InlineStates => {
2987         msg_to_decrypt => \&msg_to_decrypt,
2988         next_task => \&next_task,
2989         task_result => \&handle_task_result,
2990         task_done   => \&handle_task_done,
2991         task_debug  => \&handle_task_debug,
2992         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2993     }
2994 );
2996 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2998 # create session for repeatedly checking the job queue for jobs
2999 POE::Session->create(
3000         inline_states => {
3001                 _start => \&session_start,
3002         register_at_foreign_servers => \&register_at_foreign_servers,
3003         sig_handler => \&sig_handler,
3004         next_task => \&next_task,
3005         task_result => \&handle_task_result,
3006         task_done   => \&handle_task_done,
3007         task_debug  => \&handle_task_debug,
3008         watch_for_next_tasks => \&watch_for_next_tasks,
3009         watch_for_new_messages => \&watch_for_new_messages,
3010         watch_for_delivery_messages => \&watch_for_delivery_messages,
3011         watch_for_done_messages => \&watch_for_done_messages,
3012                 watch_for_new_jobs => \&watch_for_new_jobs,
3013         watch_for_done_jobs => \&watch_for_done_jobs,
3014         watch_for_old_known_clients => \&watch_for_old_known_clients,
3015         create_packages_list_db => \&run_create_packages_list_db,
3016         create_fai_server_db => \&run_create_fai_server_db,
3017         create_fai_release_db => \&run_create_fai_release_db,
3018         session_run_result => \&session_run_result,
3019         session_run_debug => \&session_run_debug,
3020         session_run_done => \&session_run_done,
3021         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3022         }
3023 );
3026 # import all modules
3027 &import_modules;
3029 # TODO
3030 # check wether all modules are gosa-si valid passwd check
3034 POE::Kernel->run();
3035 exit;