Code

bugfix gosa-si-server: update of client event module krb5.pm
[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 #===  FUNCTION  ================================================================
823 #         NAME:  get_ip 
824 #   PARAMETERS:  interface name (i.e. eth0)
825 #      RETURNS:  (ip address) 
826 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
827 #===============================================================================
828 sub get_ip {
829         my $ifreq= shift;
830         my $result= "";
831         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
832         my $proto= getprotobyname('ip');
834         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
835                 or die "socket: $!";
837         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
838                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
839                 my ($port, $addr) = sockaddr_in $sin;
840                 my $ip            = inet_ntoa $addr;
842                 if ($ip && length($ip) > 0) {
843                         $result = $ip;
844                 }
845         }
847         return $result;
851 sub get_local_ip_for_remote_ip {
852         my $remote_ip= shift;
853         my $result="0.0.0.0";
855         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
856                 if($remote_ip eq "127.0.0.1") {
857                         $result = "127.0.0.1";
858                 } else {
859                         my $PROC_NET_ROUTE= ('/proc/net/route');
861                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
862                                 or die "Could not open $PROC_NET_ROUTE";
864                         my @ifs = <PROC_NET_ROUTE>;
866                         close(PROC_NET_ROUTE);
868                         # Eat header line
869                         shift @ifs;
870                         chomp @ifs;
871                         foreach my $line(@ifs) {
872                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
873                                 my $destination;
874                                 my $mask;
875                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
876                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
877                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
878                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
879                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
880                                         # destination matches route, save mac and exit
881                                         $result= &get_ip($Iface);
882                                         last;
883                                 }
884                         }
885                 }
886         } else {
887                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
888         }
889         return $result;
893 sub send_msg_to_target {
894     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
895     my $error = 0;
896     my $header;
897     my $timestamp = &get_time();
898     my $new_status;
899     my $act_status;
900     my ($sql_statement, $res);
901   
902     if( $msg_header ) {
903         $header = "'$msg_header'-";
904     } else {
905         $header = "";
906     }
908         # Patch the source ip
909         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
910                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
911                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
912         }
914     # encrypt xml msg
915     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
917     # opensocket
918     my $socket = &open_socket($address);
919     if( !$socket ) {
920         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
921         $error++;
922     }
923     
924     if( $error == 0 ) {
925         # send xml msg
926         print $socket $crypted_msg."\n";
928         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
929         daemon_log("$session_id DEBUG: message:\n$msg", 9);
930         
931     }
933     # close socket in any case
934     if( $socket ) {
935         close $socket;
936     }
938     if( $error > 0 ) { $new_status = "down"; }
939     else { $new_status = $msg_header; }
942     # known_clients
943     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
944     $res = $known_clients_db->select_dbentry($sql_statement);
945     if( keys(%$res) == 1) {
946         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
947         if ($act_status eq "down" && $new_status eq "down") {
948             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
949             $res = $known_clients_db->del_dbentry($sql_statement);
950             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
951         } else { 
952             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
953             $res = $known_clients_db->update_dbentry($sql_statement);
954             if($new_status eq "down"){
955                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
956             } else {
957                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
958             }
959         }
960     }
962     # known_server
963     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
964     $res = $known_server_db->select_dbentry($sql_statement);
965     if( keys(%$res) == 1) {
966         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967         if ($act_status eq "down" && $new_status eq "down") {
968             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
969             $res = $known_server_db->del_dbentry($sql_statement);
970             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
971         } 
972         else { 
973             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
974             $res = $known_server_db->update_dbentry($sql_statement);
975             if($new_status eq "down"){
976                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
977             } else {
978                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
979             }
980         }
981     }
982     return $error; 
986 sub update_jobdb_status_for_send_msgs {
987     my ($answer, $error) = @_;
988     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
989         my $jobdb_id = $1;
990             
991         # sending msg faild
992         if( $error ) {
993             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
994                 my $sql_statement = "UPDATE $job_queue_tn ".
995                     "SET status='error', result='can not deliver msg, please consult log file' ".
996                     "WHERE id=$jobdb_id";
997                 my $res = $job_db->update_dbentry($sql_statement);
998             }
1000         # sending msg was successful
1001         } else {
1002             my $sql_statement = "UPDATE $job_queue_tn ".
1003                 "SET status='done' ".
1004                 "WHERE id=$jobdb_id AND status='processed'";
1005             my $res = $job_db->update_dbentry($sql_statement);
1006         }
1007     }
1011 sub sig_handler {
1012         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013         daemon_log("0 INFO got signal '$signal'", 1); 
1014         $kernel->sig_handled();
1015         return;
1019 sub msg_to_decrypt {
1020     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1021     my $session_id = $session->ID;
1022     my ($msg, $msg_hash, $module);
1023     my $error = 0;
1025     # hole neue msg aus @msgs_to_decrypt
1026     my $next_msg = shift @msgs_to_decrypt;
1027     
1028     # entschlüssle sie
1030     # msg is from a new client or gosa
1031     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032     # msg is from a gosa-si-server or gosa-si-bus
1033     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035     }
1036     # msg is from a gosa-si-client
1037     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039     }
1040     # an error occurred
1041     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043         # could not understand a msg from its server the client cause a re-registering process
1044         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1045                 "' to cause a re-registering of the client if necessary", 5);
1046         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1047         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1048         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1049             my $host_name = $hit->{'hostname'};
1050             my $host_key = $hit->{'hostkey'};
1051             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1052             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1053             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1054         }
1055         $error++;
1056     }
1059     my $header;
1060     my $target;
1061     my $source;
1062     my $done = 0;
1063     my $sql;
1064     my $res;
1065     # check whether this message should be processed here
1066     if ($error == 0) {
1067         $header = @{$msg_hash->{'header'}}[0];
1068         $target = @{$msg_hash->{'target'}}[0];
1069         $source = @{$msg_hash->{'source'}}[0];
1071         # target and source is equal to GOSA -> process here
1072         if (not $done) {
1073             if ($target eq "GOSA" && $source eq "GOSA") {
1074                 $done = 1;                    
1075             }
1076         }
1078         # target is own address without forward_to_gosa-tag -> process here
1079         if (not $done) {
1080             if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1081                 $done = 1;
1082                 if ($source eq "GOSA") {
1083                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1084                 }
1085                 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1086             }
1087         }
1089         # target is a client address in known_clients -> process here
1090         if (not $done) {
1091             $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1092             $res = $known_clients_db->select_dbentry($sql);
1093             if (keys(%$res) > 0) {
1094                 $done = 1; 
1095                 my $hostname = $res->{1}->{'hostname'};
1096                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1097                 print STDERR "target is a client address in known_clients -> process here\n";
1098             }
1099         }
1101         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1102         if (not $done) {
1103             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1104             my $gosa_at;
1105             my $gosa_session_id;
1106             if (($target eq $server_address) && (defined $forward_to_gosa)){
1107                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1108                 if ($gosa_at ne $server_address) {
1109                     $done = 1;
1110                     print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1111                 }
1112             }
1113         }
1115         # if message should be processed here -> add message to incoming_db
1116         if ($done) {
1118             # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1119             # so gosa-si-server knows how to process this kind of messages
1120             if ($header =~ /^gosa_/ || $header =~ /job_/) {
1121                 $module = "GosaPackages";
1122             }
1124             my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1125                     primkey=>[],
1126                     headertag=>$header,
1127                     targettag=>$target,
1128                     xmlmessage=>$msg,
1129                     timestamp=>&get_time,
1130                     module=>$module,
1131                     sessionid=>$session_id,
1132                     } );
1134         }
1136         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1137         if (not $done) {
1138             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1139             my $gosa_at;
1140             my $gosa_session_id;
1141             if (($target eq $server_address) && (defined $forward_to_gosa)){
1142                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1143                 if ($gosa_at eq $server_address) {
1144                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1145                     if( defined $session_reference ) {
1146                         $heap = $session_reference->get_heap();
1147                     }
1148                     if(exists $heap->{'client'}) {
1149                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1150                         $heap->{'client'}->put($msg);
1151                     }
1152                     $done = 1;
1153                     print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1154                 }
1155             }
1157         }
1159         # target is a client address in foreign_clients -> forward to registration server
1160         if (not $done) {
1161             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1162             $res = $foreign_clients_db->select_dbentry($sql);
1163             if (keys(%$res) > 0) {
1164                 my $hostname = $res->{1}->{'hostname'};
1165                 my $regserver = $res->{1}->{'regserver'};
1166                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1167                 my $res = $known_server_db->select_dbentry($sql);
1168                 if (keys(%$res) > 0) {
1169                     my $regserver_key = $res->{1}->{'hostkey'};
1170                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1171                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1172                     if ($source eq "GOSA") {
1173                         $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1174                     }
1175                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1176                 }
1177                 $done = 1;
1178                 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1179             }
1180         }
1182         # target is a server address -> forward to server
1183         if (not $done) {
1184             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1185             $res = $known_server_db->select_dbentry($sql);
1186             if (keys(%$res) > 0) {
1187                 my $hostkey = $res->{1}->{'hostkey'};
1189                 if ($source eq "GOSA") {
1190                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1191                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1193                 }
1195                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1196                 $done = 1;
1197                 print STDERR "target is a server address -> forward to server\n";
1198             }
1201         }
1203         if (not $done) {
1204             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1205         }
1206     }
1208     return;
1212 sub next_task {
1213     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1214     my $running_task = POE::Wheel::Run->new(
1215             Program => sub { process_task($session, $heap, $task) },
1216             StdioFilter => POE::Filter::Reference->new(),
1217             StdoutEvent  => "task_result",
1218             StderrEvent  => "task_debug",
1219             CloseEvent   => "task_done",
1220             );
1221     $heap->{task}->{ $running_task->ID } = $running_task;
1224 sub handle_task_result {
1225     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1226     my $client_answer = $result->{'answer'};
1227     if( $client_answer =~ s/session_id=(\d+)$// ) {
1228         my $session_id = $1;
1229         if( defined $session_id ) {
1230             my $session_reference = $kernel->ID_id_to_session($session_id);
1231             if( defined $session_reference ) {
1232                 $heap = $session_reference->get_heap();
1233             }
1234         }
1236         if(exists $heap->{'client'}) {
1237             $heap->{'client'}->put($client_answer);
1238         }
1239     }
1240     $kernel->sig(CHLD => "child_reap");
1243 sub handle_task_debug {
1244     my $result = $_[ARG0];
1245     print STDERR "$result\n";
1248 sub handle_task_done {
1249     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1250     delete $heap->{task}->{$task_id};
1253 sub process_task {
1254     no strict "refs";
1255     my ($session, $heap, $task) = @_;
1256     my $error = 0;
1257     my $answer_l;
1258     my ($answer_header, @answer_target_l, $answer_source);
1259     my $client_answer = "";
1261     # prepare all variables needed to process message
1262     my $msg = $task->{'xmlmessage'};
1263     my $incoming_id = $task->{'id'};
1264     my $module = $task->{'module'};
1265     my $header =  $task->{'headertag'};
1266     my $session_id = $task->{'sessionid'};
1267     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1268     my $source = @{$msg_hash->{'source'}}[0];
1269     
1270     # set timestamp of incoming client uptodate, so client will not 
1271     # be deleted from known_clients because of expiration
1272     my $act_time = &get_time();
1273     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1274     my $res = $known_clients_db->exec_statement($sql);
1276     ######################
1277     # process incoming msg
1278     if( $error == 0) {
1279         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1280         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1281         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1283         if ( 0 < @{$answer_l} ) {
1284             my $answer_str = join("\n", @{$answer_l});
1285             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1286                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1287             }
1288             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1289         } else {
1290             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1291         }
1293     }
1294     if( !$answer_l ) { $error++ };
1296     ########
1297     # answer
1298     if( $error == 0 ) {
1300         foreach my $answer ( @{$answer_l} ) {
1301             # check outgoing msg to xml validity
1302             my $answer_hash = &check_outgoing_xml_validity($answer);
1303             if( not defined $answer_hash ) { next; }
1304             
1305             $answer_header = @{$answer_hash->{'header'}}[0];
1306             @answer_target_l = @{$answer_hash->{'target'}};
1307             $answer_source = @{$answer_hash->{'source'}}[0];
1309             # deliver msg to all targets 
1310             foreach my $answer_target ( @answer_target_l ) {
1312                 # targets of msg are all gosa-si-clients in known_clients_db
1313                 if( $answer_target eq "*" ) {
1314                     # answer is for all clients
1315                     my $sql_statement= "SELECT * FROM known_clients";
1316                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1317                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1318                         my $host_name = $hit->{hostname};
1319                         my $host_key = $hit->{hostkey};
1320                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1321                         &update_jobdb_status_for_send_msgs($answer, $error);
1322                     }
1323                 }
1325                 # targets of msg are all gosa-si-server in known_server_db
1326                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1327                     # answer is for all server in known_server
1328                     my $sql_statement= "SELECT * FROM $known_server_tn";
1329                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1330                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1331                         my $host_name = $hit->{hostname};
1332                         my $host_key = $hit->{hostkey};
1333                         $answer =~ s/<target>KNOWN_SERVER<\/target>/<target>$host_name<\/target>/g;
1334                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1335                         &update_jobdb_status_for_send_msgs($answer, $error);
1336                     }
1337                 }
1339                 # target of msg is GOsa
1340                                 elsif( $answer_target eq "GOSA" ) {
1341                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1342                                         my $add_on = "";
1343                     if( defined $session_id ) {
1344                         $add_on = ".session_id=$session_id";
1345                     }
1346                     # answer is for GOSA and has to returned to connected client
1347                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1348                     $client_answer = $gosa_answer.$add_on;
1349                 }
1351                 # target of msg is job queue at this host
1352                 elsif( $answer_target eq "JOBDB") {
1353                     $answer =~ /<header>(\S+)<\/header>/;   
1354                     my $header;
1355                     if( defined $1 ) { $header = $1; }
1356                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1357                     &update_jobdb_status_for_send_msgs($answer, $error);
1358                 }
1360                 # target of msg is a mac address
1361                 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 ) {
1362                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1363                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1364                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1365                     my $found_ip_flag = 0;
1366                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1367                         my $host_name = $hit->{hostname};
1368                         my $host_key = $hit->{hostkey};
1369                         $answer =~ s/$answer_target/$host_name/g;
1370                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1371                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1372                         &update_jobdb_status_for_send_msgs($answer, $error);
1373                         $found_ip_flag++ ;
1374                     }   
1375                     if( $found_ip_flag == 0) {
1376                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1377                         if( $bus_activ eq "true" ) { 
1378                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1379                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1380                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1381                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1382                                 my $bus_address = $hit->{hostname};
1383                                 my $bus_key = $hit->{hostkey};
1384                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1385                                 &update_jobdb_status_for_send_msgs($answer, $error);
1386                                 last;
1387                             }
1388                         }
1390                     }
1392                 #  answer is for one specific host   
1393                 } else {
1394                     # get encrypt_key
1395                     my $encrypt_key = &get_encrypt_key($answer_target);
1396                     if( not defined $encrypt_key ) {
1397                         # unknown target, forward msg to bus
1398                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1399                         if( $bus_activ eq "true" ) { 
1400                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1401                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1402                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1403                             my $res_length = keys( %{$query_res} );
1404                             if( $res_length == 0 ){
1405                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1406                                         "no bus found in known_server", 3);
1407                             }
1408                             else {
1409                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1410                                     my $bus_key = $hit->{hostkey};
1411                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1412                                     &update_jobdb_status_for_send_msgs($answer, $error);
1413                                 }
1414                             }
1415                         }
1416                         next;
1417                     }
1418                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1419                     &update_jobdb_status_for_send_msgs($answer, $error);
1420                 }
1421             }
1422         }
1423     }
1425     my $filter = POE::Filter::Reference->new();
1426     my %result = ( 
1427             status => "seems ok to me",
1428             answer => $client_answer,
1429             );
1431     my $output = $filter->put( [ \%result ] );
1432     print @$output;
1437 sub session_start {
1438     my ($kernel) = $_[KERNEL];
1439     &trigger_db_loop($kernel);
1440     $global_kernel = $kernel;
1441     $kernel->yield('register_at_foreign_servers');
1442         $kernel->yield('create_fai_server_db', $fai_server_tn );
1443         $kernel->yield('create_fai_release_db', $fai_release_tn );
1444     $kernel->yield('watch_for_next_tasks');
1445         $kernel->sig(USR1 => "sig_handler");
1446         $kernel->sig(USR2 => "create_packages_list_db");
1447         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1448         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1449         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1450     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1451         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1452     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1456 sub trigger_db_loop {
1457         my ($kernel) = @_ ;
1458 #       $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1459 #       $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1460 #       $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1461 #    $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1462 #       $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1463 #    $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1467 sub watch_for_done_jobs {
1468     my ($kernel,$heap) = @_[KERNEL, HEAP];
1470     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1471         my $res = $job_db->select_dbentry( $sql_statement );
1473     while( my ($id, $hit) = each %{$res} ) {
1474         my $jobdb_id = $hit->{id};
1475         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1476         my $res = $job_db->del_dbentry($sql_statement); 
1477     }
1479     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1483 sub watch_for_new_jobs {
1484         if($watch_for_new_jobs_in_progress == 0) {
1485                 $watch_for_new_jobs_in_progress = 1;
1486                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1488                 # check gosa job queue for jobs with executable timestamp
1489                 my $timestamp = &get_time();
1490                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1491                 my $res = $job_db->exec_statement( $sql_statement );
1493                 # Merge all new jobs that would do the same actions
1494                 my @drops;
1495                 my $hits;
1496                 foreach my $hit (reverse @{$res} ) {
1497                         my $macaddress= lc @{$hit}[8];
1498                         my $headertag= @{$hit}[5];
1499                         if(
1500                                 defined($hits->{$macaddress}) &&
1501                                 defined($hits->{$macaddress}->{$headertag}) &&
1502                                 defined($hits->{$macaddress}->{$headertag}[0])
1503                         ) {
1504                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1505                         }
1506                         $hits->{$macaddress}->{$headertag}= $hit;
1507                 }
1509                 # Delete new jobs with a matching job in state 'processing'
1510                 foreach my $macaddress (keys %{$hits}) {
1511                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1512                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1513                                 if(defined($jobdb_id)) {
1514                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1515                                         my $res = $job_db->exec_statement( $sql_statement );
1516                                         foreach my $hit (@{$res}) {
1517                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1518                                         }
1519                                 } else {
1520                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1521                                 }
1522                         }
1523                 }
1525                 # Commit deletion
1526                 $job_db->exec_statementlist(\@drops);
1528                 # Look for new jobs that could be executed
1529                 foreach my $macaddress (keys %{$hits}) {
1531                         # Look if there is an executing job
1532                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1533                         my $res = $job_db->exec_statement( $sql_statement );
1535                         # Skip new jobs for host if there is a processing job
1536                         if(defined($res) and defined @{$res}[0]) {
1537                                 next;
1538                         }
1540                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1541                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1542                                 if(defined($jobdb_id)) {
1543                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1545                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1546                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1547                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1549                                         # expect macaddress is unique!!!!!!
1550                                         my $target = $res_hash->{1}->{hostname};
1552                                         # change header
1553                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1555                                         # add sqlite_id
1556                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1558                                         $job_msg =~ /<header>(\S+)<\/header>/;
1559                                         my $header = $1 ;
1560                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1562                                         # update status in job queue to 'processing'
1563                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1564                                         my $res = $job_db->update_dbentry($sql_statement);
1565 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1567                                         # We don't want parallel processing
1568                                         last;
1569                                 }
1570                         }
1571                 }
1573                 $watch_for_new_jobs_in_progress = 0;
1574                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1575         }
1579 sub watch_for_new_messages {
1580     my ($kernel,$heap) = @_[KERNEL, HEAP];
1581     my @coll_user_msg;   # collection list of outgoing messages
1582     
1583     # check messaging_db for new incoming messages with executable timestamp
1584     my $timestamp = &get_time();
1585     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1586     my $res = $messaging_db->exec_statement( $sql_statement );
1587         foreach my $hit (@{$res}) {
1589         # create outgoing messages
1590         my $message_to = @{$hit}[3];
1591         # translate message_to to plain login name
1592         my @message_to_l = split(/,/, $message_to);  
1593                 my %receiver_h; 
1594                 foreach my $receiver (@message_to_l) {
1595                         if ($receiver =~ /^u_([\s\S]*)$/) {
1596                                 $receiver_h{$1} = 0;
1597                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1598                                 my $group_name = $1;
1599                                 # fetch all group members from ldap and add them to receiver hash
1600                                 my $ldap_handle = &get_ldap_handle();
1601                                 if (defined $ldap_handle) {
1602                                                 my $mesg = $ldap_handle->search(
1603                                                                                 base => $ldap_base,
1604                                                                                 scope => 'sub',
1605                                                                                 attrs => ['memberUid'],
1606                                                                                 filter => "cn=$group_name",
1607                                                                                 );
1608                                                 if ($mesg->count) {
1609                                                                 my @entries = $mesg->entries;
1610                                                                 foreach my $entry (@entries) {
1611                                                                                 my @receivers= $entry->get_value("memberUid");
1612                                                                                 foreach my $receiver (@receivers) { 
1613                                                                                                 $receiver_h{$1} = 0;
1614                                                                                 }
1615                                                                 }
1616                                                 } 
1617                                                 # translating errors ?
1618                                                 if ($mesg->code) {
1619                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1620                                                 }
1621                                 # ldap handle error ?           
1622                                 } else {
1623                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1624                                 }
1625                         } else {
1626                                 my $sbjct = &encode_base64(@{$hit}[1]);
1627                                 my $msg = &encode_base64(@{$hit}[7]);
1628                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1629                         }
1630                 }
1631                 my @receiver_l = keys(%receiver_h);
1633         my $message_id = @{$hit}[0];
1635         #add each outgoing msg to messaging_db
1636         my $receiver;
1637         foreach $receiver (@receiver_l) {
1638             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1639                 "VALUES ('".
1640                 $message_id."', '".    # id
1641                 @{$hit}[1]."', '".     # subject
1642                 @{$hit}[2]."', '".     # message_from
1643                 $receiver."', '".      # message_to
1644                 "none"."', '".         # flag
1645                 "out"."', '".          # direction
1646                 @{$hit}[6]."', '".     # delivery_time
1647                 @{$hit}[7]."', '".     # message
1648                 $timestamp."'".     # timestamp
1649                 ")";
1650             &daemon_log("M DEBUG: $sql_statement", 1);
1651             my $res = $messaging_db->exec_statement($sql_statement);
1652             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1653         }
1655         # set incoming message to flag d=deliverd
1656         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1657         &daemon_log("M DEBUG: $sql_statement", 7);
1658         $res = $messaging_db->update_dbentry($sql_statement);
1659         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1660     }
1662     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1663     return;
1666 sub watch_for_delivery_messages {
1667     my ($kernel, $heap) = @_[KERNEL, HEAP];
1669     # select outgoing messages
1670     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1671     #&daemon_log("0 DEBUG: $sql", 7);
1672     my $res = $messaging_db->exec_statement( $sql_statement );
1673     
1674     # build out msg for each    usr
1675     foreach my $hit (@{$res}) {
1676         my $receiver = @{$hit}[3];
1677         my $msg_id = @{$hit}[0];
1678         my $subject = @{$hit}[1];
1679         my $message = @{$hit}[7];
1681         # resolve usr -> host where usr is logged in
1682         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1683         #&daemon_log("0 DEBUG: $sql", 7);
1684         my $res = $login_users_db->exec_statement($sql);
1686         # reciver is logged in nowhere
1687         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1689                 my $send_succeed = 0;
1690                 foreach my $hit (@$res) {
1691                                 my $receiver_host = @$hit[0];
1692                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1694                                 # fetch key to encrypt msg propperly for usr/host
1695                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1696                                 &daemon_log("0 DEBUG: $sql", 7);
1697                                 my $res = $known_clients_db->select_dbentry($sql);
1699                                 # host is already down
1700                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1702                                 # host is on
1703                                 my $receiver_key = @{@{$res}[0]}[2];
1704                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1705                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1706                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1707                                 if ($error == 0 ) {
1708                                         $send_succeed++ ;
1709                                 }
1710                 }
1712                 if ($send_succeed) {
1713                                 # set outgoing msg at db to deliverd
1714                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1715                                 &daemon_log("0 DEBUG: $sql", 7);
1716                                 my $res = $messaging_db->exec_statement($sql); 
1717                 }
1718         }
1720     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1721     return;
1725 sub watch_for_done_messages {
1726     my ($kernel,$heap) = @_[KERNEL, HEAP];
1728     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1729     #&daemon_log("0 DEBUG: $sql", 7);
1730     my $res = $messaging_db->exec_statement($sql); 
1732     foreach my $hit (@{$res}) {
1733         my $msg_id = @{$hit}[0];
1735         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1736         #&daemon_log("0 DEBUG: $sql", 7); 
1737         my $res = $messaging_db->exec_statement($sql);
1739         # not all usr msgs have been seen till now
1740         if ( ref(@$res[0]) eq "ARRAY") { next; }
1741         
1742         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1743         #&daemon_log("0 DEBUG: $sql", 7);
1744         $res = $messaging_db->exec_statement($sql);
1745     
1746     }
1748     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1749     return;
1753 sub watch_for_old_known_clients {
1754     my ($kernel,$heap) = @_[KERNEL, HEAP];
1756     my $sql_statement = "SELECT * FROM $known_clients_tn";
1757     my $res = $known_clients_db->select_dbentry( $sql_statement );
1759     my $act_time = int(&get_time());
1761     while ( my ($hit_num, $hit) = each %$res) {
1762         my $expired_timestamp = int($hit->{'timestamp'});
1763         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1764         my $dt = DateTime->new( year   => $1,
1765                 month  => $2,
1766                 day    => $3,
1767                 hour   => $4,
1768                 minute => $5,
1769                 second => $6,
1770                 );
1772         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1773         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1774         if ($act_time > $expired_timestamp) {
1775             my $hostname = $hit->{'hostname'};
1776             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1777             my $del_res = $known_clients_db->exec_statement($del_sql);
1779             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1780         }
1782     }
1784     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1788 sub watch_for_next_tasks {
1789     my ($kernel,$heap) = @_[KERNEL, HEAP];
1791     my $sql = "SELECT * FROM $incoming_tn";
1792     my $res = $incoming_db->select_dbentry($sql);
1794     while ( my ($hit_num, $hit) = each %$res) {
1795         my $headertag = $hit->{'headertag'};
1796         if ($headertag =~ /^answer_(\d+)/) {
1797             # do not start processing, this message is for a still running POE::Wheel
1798             next;
1799         }
1800         my $message_id = $hit->{'id'};
1801         $kernel->yield('next_task', $hit);
1803         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1804         my $res = $incoming_db->exec_statement($sql);
1805     }
1807     $kernel->delay_set('watch_for_next_tasks', 1); 
1811 sub get_ldap_handle {
1812         my ($session_id) = @_;
1813         my $heap;
1814         my $ldap_handle;
1816         if (not defined $session_id ) { $session_id = 0 };
1817         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1819         if ($session_id == 0) {
1820                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1821                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1822                 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password); 
1824         } else {
1825                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1826                 if( defined $session_reference ) {
1827                         $heap = $session_reference->get_heap();
1828                 }
1830                 if (not defined $heap) {
1831                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1832                         return;
1833                 }
1835                 # TODO: This "if" is nonsense, because it doesn't prove that the
1836                 #       used handle is still valid - or if we've to reconnect...
1837                 #if (not exists $heap->{ldap_handle}) {
1838                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1839                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1840                         $heap->{ldap_handle} = $ldap_handle;
1841                 #}
1842         }
1843         return $ldap_handle;
1847 sub change_fai_state {
1848     my ($st, $targets, $session_id) = @_;
1849     $session_id = 0 if not defined $session_id;
1850     # Set FAI state to localboot
1851     my %mapActions= (
1852         reboot    => '',
1853         update    => 'softupdate',
1854         localboot => 'localboot',
1855         reinstall => 'install',
1856         rescan    => '',
1857         wake      => '',
1858         memcheck  => 'memcheck',
1859         sysinfo   => 'sysinfo',
1860         install   => 'install',
1861     );
1863     # Return if this is unknown
1864     if (!exists $mapActions{ $st }){
1865         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1866       return;
1867     }
1869     my $state= $mapActions{ $st };
1871     my $ldap_handle = &get_ldap_handle($session_id);
1872     if( defined($ldap_handle) ) {
1874       # Build search filter for hosts
1875         my $search= "(&(objectClass=GOhard)";
1876         foreach (@{$targets}){
1877             $search.= "(macAddress=$_)";
1878         }
1879         $search.= ")";
1881       # If there's any host inside of the search string, procress them
1882         if (!($search =~ /macAddress/)){
1883             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1884             return;
1885         }
1887       # Perform search for Unit Tag
1888       my $mesg = $ldap_handle->search(
1889           base   => $ldap_base,
1890           scope  => 'sub',
1891           attrs  => ['dn', 'FAIstate', 'objectClass'],
1892           filter => "$search"
1893           );
1895           if ($mesg->count) {
1896                   my @entries = $mesg->entries;
1897                   foreach my $entry (@entries) {
1898                           # Only modify entry if it is not set to '$state'
1899                           if ($entry->get_value("FAIstate") ne "$state"){
1900                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1901                                   my $result;
1902                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1903                                   if (exists $tmp{'FAIobject'}){
1904                                           if ($state eq ''){
1905                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1906                                                           delete => [ FAIstate => [] ] ]);
1907                                           } else {
1908                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1909                                                           replace => [ FAIstate => $state ] ]);
1910                                           }
1911                                   } elsif ($state ne ''){
1912                                           $result= $ldap_handle->modify($entry->dn, changes => [
1913                                                   add     => [ objectClass => 'FAIobject' ],
1914                                                   add     => [ FAIstate => $state ] ]);
1915                                   }
1917                                   # Errors?
1918                                   if ($result->code){
1919                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1920                                   }
1921                           } else {
1922                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1923                           }  
1924                   }
1925           }
1926     # if no ldap handle defined
1927     } else {
1928         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1929     }
1934 sub change_goto_state {
1935     my ($st, $targets, $session_id) = @_;
1936     $session_id = 0  if not defined $session_id;
1938     # Switch on or off?
1939     my $state= $st eq 'active' ? 'active': 'locked';
1941     my $ldap_handle = &get_ldap_handle($session_id);
1942     if( defined($ldap_handle) ) {
1944       # Build search filter for hosts
1945       my $search= "(&(objectClass=GOhard)";
1946       foreach (@{$targets}){
1947         $search.= "(macAddress=$_)";
1948       }
1949       $search.= ")";
1951       # If there's any host inside of the search string, procress them
1952       if (!($search =~ /macAddress/)){
1953         return;
1954       }
1956       # Perform search for Unit Tag
1957       my $mesg = $ldap_handle->search(
1958           base   => $ldap_base,
1959           scope  => 'sub',
1960           attrs  => ['dn', 'gotoMode'],
1961           filter => "$search"
1962           );
1964       if ($mesg->count) {
1965         my @entries = $mesg->entries;
1966         foreach my $entry (@entries) {
1968           # Only modify entry if it is not set to '$state'
1969           if ($entry->get_value("gotoMode") ne $state){
1971             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1972             my $result;
1973             $result= $ldap_handle->modify($entry->dn, changes => [
1974                                                 replace => [ gotoMode => $state ] ]);
1976             # Errors?
1977             if ($result->code){
1978               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1979             }
1981           }
1982         }
1983       }
1985     }
1989 sub run_create_fai_server_db {
1990     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1991     my $session_id = $session->ID;
1992     my $task = POE::Wheel::Run->new(
1993             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1994             StdoutEvent  => "session_run_result",
1995             StderrEvent  => "session_run_debug",
1996             CloseEvent   => "session_run_done",
1997             );
1999     $heap->{task}->{ $task->ID } = $task;
2000     return;
2004 sub create_fai_server_db {
2005     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2006         my $result;
2008         if (not defined $session_id) { $session_id = 0; }
2009     my $ldap_handle = &get_ldap_handle();
2010         if(defined($ldap_handle)) {
2011                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2012                 my $mesg= $ldap_handle->search(
2013                         base   => $ldap_base,
2014                         scope  => 'sub',
2015                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2016                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2017                 );
2018                 if($mesg->{'resultCode'} == 0 &&
2019                    $mesg->count != 0) {
2020                    foreach my $entry (@{$mesg->{entries}}) {
2021                            if($entry->exists('FAIrepository')) {
2022                                    # Add an entry for each Repository configured for server
2023                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2024                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2025                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2026                                                    $result= $fai_server_db->add_dbentry( { 
2027                                                                    table => $table_name,
2028                                                                    primkey => ['server', 'release', 'tag'],
2029                                                                    server => $tmp_url,
2030                                                                    release => $tmp_release,
2031                                                                    sections => $tmp_sections,
2032                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2033                                                            } );
2034                                            }
2035                                    }
2036                            }
2037                    }
2038                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2040                 # TODO: Find a way to post the 'create_packages_list_db' event
2041                 if(not defined($dont_create_packages_list)) {
2042                         &create_packages_list_db(undef, undef, $session_id);
2043                 }
2044         }       
2045     
2046     $ldap_handle->disconnect;
2047         return $result;
2051 sub run_create_fai_release_db {
2052     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2053         my $session_id = $session->ID;
2054     my $task = POE::Wheel::Run->new(
2055             Program => sub { &create_fai_release_db($table_name, $session_id) },
2056             StdoutEvent  => "session_run_result",
2057             StderrEvent  => "session_run_debug",
2058             CloseEvent   => "session_run_done",
2059             );
2061     $heap->{task}->{ $task->ID } = $task;
2062     return;
2066 sub create_fai_release_db {
2067         my ($table_name, $session_id) = @_;
2068         my $result;
2070     # used for logging
2071     if (not defined $session_id) { $session_id = 0; }
2073     my $ldap_handle = &get_ldap_handle();
2074         if(defined($ldap_handle)) {
2075                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2076                 my $mesg= $ldap_handle->search(
2077                         base   => $ldap_base,
2078                         scope  => 'sub',
2079                         attrs  => [],
2080                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2081                 );
2082                 if($mesg->{'resultCode'} == 0 &&
2083                         $mesg->count != 0) {
2084                         # Walk through all possible FAI container ou's
2085                         my @sql_list;
2086                         my $timestamp= &get_time();
2087                         foreach my $ou (@{$mesg->{entries}}) {
2088                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2089                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2090                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2091                                         if(@tmp_array) {
2092                                                 foreach my $entry (@tmp_array) {
2093                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2094                                                                 my $sql= 
2095                                                                 "INSERT INTO $table_name "
2096                                                                 ."(timestamp, release, class, type, state) VALUES ("
2097                                                                 .$timestamp.","
2098                                                                 ."'".$entry->{'release'}."',"
2099                                                                 ."'".$entry->{'class'}."',"
2100                                                                 ."'".$entry->{'type'}."',"
2101                                                                 ."'".$entry->{'state'}."')";
2102                                                                 push @sql_list, $sql;
2103                                                         }
2104                                                 }
2105                                         }
2106                                 }
2107                         }
2109                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2110                         if(@sql_list) {
2111                                 unshift @sql_list, "VACUUM";
2112                                 unshift @sql_list, "DELETE FROM $table_name";
2113                                 $fai_release_db->exec_statementlist(\@sql_list);
2114                         }
2115                         daemon_log("$session_id DEBUG: Done with inserting",7);
2116                 }
2117                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2118         }
2119     $ldap_handle->disconnect;
2120         return $result;
2123 sub get_fai_types {
2124         my $tmp_classes = shift || return undef;
2125         my @result;
2127         foreach my $type(keys %{$tmp_classes}) {
2128                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2129                         my $entry = {
2130                                 type => $type,
2131                                 state => $tmp_classes->{$type}[0],
2132                         };
2133                         push @result, $entry;
2134                 }
2135         }
2137         return @result;
2140 sub get_fai_state {
2141         my $result = "";
2142         my $tmp_classes = shift || return $result;
2144         foreach my $type(keys %{$tmp_classes}) {
2145                 if(defined($tmp_classes->{$type}[0])) {
2146                         $result = $tmp_classes->{$type}[0];
2147                         
2148                 # State is equal for all types in class
2149                         last;
2150                 }
2151         }
2153         return $result;
2156 sub resolve_fai_classes {
2157         my ($fai_base, $ldap_handle, $session_id) = @_;
2158         if (not defined $session_id) { $session_id = 0; }
2159         my $result;
2160         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2161         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2162         my $fai_classes;
2164         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2165         my $mesg= $ldap_handle->search(
2166                 base   => $fai_base,
2167                 scope  => 'sub',
2168                 attrs  => ['cn','objectClass','FAIstate'],
2169                 filter => $fai_filter,
2170         );
2171         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2173         if($mesg->{'resultCode'} == 0 &&
2174                 $mesg->count != 0) {
2175                 foreach my $entry (@{$mesg->{entries}}) {
2176                         if($entry->exists('cn')) {
2177                                 my $tmp_dn= $entry->dn();
2179                                 # Skip classname and ou dn parts for class
2180                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2182                                 # Skip classes without releases
2183                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2184                                         next;
2185                                 }
2187                                 my $tmp_cn= $entry->get_value('cn');
2188                                 my $tmp_state= $entry->get_value('FAIstate');
2190                                 my $tmp_type;
2191                                 # Get FAI type
2192                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2193                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2194                                                 $tmp_type= $oclass;
2195                                                 last;
2196                                         }
2197                                 }
2199                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2200                                         # A Subrelease
2201                                         my @sub_releases = split(/,/, $tmp_release);
2203                                         # Walk through subreleases and build hash tree
2204                                         my $hash;
2205                                         while(my $tmp_sub_release = pop @sub_releases) {
2206                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2207                                         }
2208                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2209                                 } else {
2210                                         # A branch, no subrelease
2211                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2212                                 }
2213                         } elsif (!$entry->exists('cn')) {
2214                                 my $tmp_dn= $entry->dn();
2215                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2217                                 # Skip classes without releases
2218                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2219                                         next;
2220                                 }
2222                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2223                                         # A Subrelease
2224                                         my @sub_releases= split(/,/, $tmp_release);
2226                                         # Walk through subreleases and build hash tree
2227                                         my $hash;
2228                                         while(my $tmp_sub_release = pop @sub_releases) {
2229                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2230                                         }
2231                                         # Remove the last two characters
2232                                         chop($hash);
2233                                         chop($hash);
2235                                         eval('$fai_classes->'.$hash.'= {}');
2236                                 } else {
2237                                         # A branch, no subrelease
2238                                         if(!exists($fai_classes->{$tmp_release})) {
2239                                                 $fai_classes->{$tmp_release} = {};
2240                                         }
2241                                 }
2242                         }
2243                 }
2245                 # The hash is complete, now we can honor the copy-on-write based missing entries
2246                 foreach my $release (keys %$fai_classes) {
2247                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2248                 }
2249         }
2250         return $result;
2253 sub apply_fai_inheritance {
2254        my $fai_classes = shift || return {};
2255        my $tmp_classes;
2257        # Get the classes from the branch
2258        foreach my $class (keys %{$fai_classes}) {
2259                # Skip subreleases
2260                if($class =~ /^ou=.*$/) {
2261                        next;
2262                } else {
2263                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2264                }
2265        }
2267        # Apply to each subrelease
2268        foreach my $subrelease (keys %{$fai_classes}) {
2269                if($subrelease =~ /ou=/) {
2270                        foreach my $tmp_class (keys %{$tmp_classes}) {
2271                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2272                                        $fai_classes->{$subrelease}->{$tmp_class} =
2273                                        deep_copy($tmp_classes->{$tmp_class});
2274                                } else {
2275                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2276                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2277                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2278                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2279                                                }
2280                                        }
2281                                }
2282                        }
2283                }
2284        }
2286        # Find subreleases in deeper levels
2287        foreach my $subrelease (keys %{$fai_classes}) {
2288                if($subrelease =~ /ou=/) {
2289                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2290                                if($subsubrelease =~ /ou=/) {
2291                                        apply_fai_inheritance($fai_classes->{$subrelease});
2292                                }
2293                        }
2294                }
2295        }
2297        return $fai_classes;
2300 sub get_fai_release_entries {
2301         my $tmp_classes = shift || return;
2302         my $parent = shift || "";
2303         my @result = shift || ();
2305         foreach my $entry (keys %{$tmp_classes}) {
2306                 if(defined($entry)) {
2307                         if($entry =~ /^ou=.*$/) {
2308                                 my $release_name = $entry;
2309                                 $release_name =~ s/ou=//g;
2310                                 if(length($parent)>0) {
2311                                         $release_name = $parent."/".$release_name;
2312                                 }
2313                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2314                                 foreach my $bufentry(@bufentries) {
2315                                         push @result, $bufentry;
2316                                 }
2317                         } else {
2318                                 my @types = get_fai_types($tmp_classes->{$entry});
2319                                 foreach my $type (@types) {
2320                                         push @result, 
2321                                         {
2322                                                 'class' => $entry,
2323                                                 'type' => $type->{'type'},
2324                                                 'release' => $parent,
2325                                                 'state' => $type->{'state'},
2326                                         };
2327                                 }
2328                         }
2329                 }
2330         }
2332         return @result;
2335 sub deep_copy {
2336         my $this = shift;
2337         if (not ref $this) {
2338                 $this;
2339         } elsif (ref $this eq "ARRAY") {
2340                 [map deep_copy($_), @$this];
2341         } elsif (ref $this eq "HASH") {
2342                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2343         } else { die "what type is $_?" }
2347 sub session_run_result {
2348     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2349     $kernel->sig(CHLD => "child_reap");
2352 sub session_run_debug {
2353     my $result = $_[ARG0];
2354     print STDERR "$result\n";
2357 sub session_run_done {
2358     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2359     delete $heap->{task}->{$task_id};
2363 sub create_sources_list {
2364         my $session_id = shift;
2365         my $ldap_handle = &main::get_ldap_handle;
2366         my $result="/tmp/gosa_si_tmp_sources_list";
2368         # Remove old file
2369         if(stat($result)) {
2370                 unlink($result);
2371                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2372         }
2374         my $fh;
2375         open($fh, ">$result");
2376         if (not defined $fh) {
2377                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2378                 return undef;
2379         }
2380         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2381                 my $mesg=$ldap_handle->search(
2382                         base    => $main::ldap_server_dn,
2383                         scope   => 'base',
2384                         attrs   => 'FAIrepository',
2385                         filter  => 'objectClass=FAIrepositoryServer'
2386                 );
2387                 if($mesg->count) {
2388                         foreach my $entry(@{$mesg->{'entries'}}) {
2389                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2390                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2391                                         my $line = "deb $server $release";
2392                                         $sections =~ s/,/ /g;
2393                                         $line.= " $sections";
2394                                         print $fh $line."\n";
2395                                 }
2396                         }
2397                 }
2398         } else {
2399                 if (defined $main::ldap_server_dn){
2400                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2401                 } else {
2402                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2403                 }
2404         }
2405         close($fh);
2407         return $result;
2411 sub run_create_packages_list_db {
2412     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2413         my $session_id = $session->ID;
2415         my $task = POE::Wheel::Run->new(
2416                                         Priority => +20,
2417                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2418                                         StdoutEvent  => "session_run_result",
2419                                         StderrEvent  => "session_run_debug",
2420                                         CloseEvent   => "session_run_done",
2421                                         );
2422         $heap->{task}->{ $task->ID } = $task;
2426 sub create_packages_list_db {
2427         my ($ldap_handle, $sources_file, $session_id) = @_;
2428         
2429         # it should not be possible to trigger a recreation of packages_list_db
2430         # while packages_list_db is under construction, so set flag packages_list_under_construction
2431         # which is tested befor recreation can be started
2432         if (-r $packages_list_under_construction) {
2433                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2434                 return;
2435         } else {
2436                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2437                 # set packages_list_under_construction to true
2438                 system("touch $packages_list_under_construction");
2439                 @packages_list_statements=();
2440         }
2442         if (not defined $session_id) { $session_id = 0; }
2443         if (not defined $ldap_handle) { 
2444                 $ldap_handle= &get_ldap_handle();
2446                 if (not defined $ldap_handle) {
2447                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2448                         unlink($packages_list_under_construction);
2449                         return;
2450                 }
2451         }
2452         if (not defined $sources_file) { 
2453                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2454                 $sources_file = &create_sources_list($session_id);
2455         }
2457         if (not defined $sources_file) {
2458                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2459                 unlink($packages_list_under_construction);
2460                 return;
2461         }
2463         my $line;
2465         open(CONFIG, "<$sources_file") or do {
2466                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2467                 unlink($packages_list_under_construction);
2468                 return;
2469         };
2471         # Read lines
2472         while ($line = <CONFIG>){
2473                 # Unify
2474                 chop($line);
2475                 $line =~ s/^\s+//;
2476                 $line =~ s/^\s+/ /;
2478                 # Strip comments
2479                 $line =~ s/#.*$//g;
2481                 # Skip empty lines
2482                 if ($line =~ /^\s*$/){
2483                         next;
2484                 }
2486                 # Interpret deb line
2487                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2488                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2489                         my $section;
2490                         foreach $section (split(' ', $sections)){
2491                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2492                         }
2493                 }
2494         }
2496         close (CONFIG);
2498         find(\&cleanup_and_extract, keys( %repo_dirs ));
2499         &main::strip_packages_list_statements();
2500         unshift @packages_list_statements, "VACUUM";
2501         $packages_list_db->exec_statementlist(\@packages_list_statements);
2502         unlink($packages_list_under_construction);
2503         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2504         return;
2507 # This function should do some intensive task to minimize the db-traffic
2508 sub strip_packages_list_statements {
2509     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2510         my @new_statement_list=();
2511         my $hash;
2512         my $insert_hash;
2513         my $update_hash;
2514         my $delete_hash;
2515         my $local_timestamp=get_time();
2517         foreach my $existing_entry (@existing_entries) {
2518                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2519         }
2521         foreach my $statement (@packages_list_statements) {
2522                 if($statement =~ /^INSERT/i) {
2523                         # Assign the values from the insert statement
2524                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2525                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2526                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2527                                 # If section or description has changed, update the DB
2528                                 if( 
2529                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2530                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2531                                 ) {
2532                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2533                                 }
2534                         } else {
2535                                 # Insert a non-existing entry to db
2536                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2537                         }
2538                 } elsif ($statement =~ /^UPDATE/i) {
2539                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2540                         /^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;
2541                         foreach my $distribution (keys %{$hash}) {
2542                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2543                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2544                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2545                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2546                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2547                                                 my $section;
2548                                                 my $description;
2549                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2550                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2551                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2552                                                 }
2553                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2554                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2555                                                 }
2556                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2557                                         }
2558                                 }
2559                         }
2560                 }
2561         }
2563         # TODO: Check for orphaned entries
2565         # unroll the insert_hash
2566         foreach my $distribution (keys %{$insert_hash}) {
2567                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2568                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2569                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2570                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2571                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2572                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2573                                 ."'$local_timestamp')";
2574                         }
2575                 }
2576         }
2578         # unroll the update hash
2579         foreach my $distribution (keys %{$update_hash}) {
2580                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2581                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2582                                 my $set = "";
2583                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2584                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2585                                 }
2586                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2587                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2588                                 }
2589                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2590                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2591                                 }
2592                                 if(defined($set) and length($set) > 0) {
2593                                         $set .= "timestamp = '$local_timestamp'";
2594                                 } else {
2595                                         next;
2596                                 }
2597                                 push @new_statement_list, 
2598                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2599                                         ." distribution = '$distribution'"
2600                                         ." AND package = '$package'"
2601                                         ." AND version = '$version'";
2602                         }
2603                 }
2604         }
2606         @packages_list_statements = @new_statement_list;
2610 sub parse_package_info {
2611     my ($baseurl, $dist, $section, $session_id)= @_;
2612     my ($package);
2613     if (not defined $session_id) { $session_id = 0; }
2614     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2615     $repo_dirs{ "${repo_path}/pool" } = 1;
2617     foreach $package ("Packages.gz"){
2618         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2619         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2620         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2621     }
2622     
2626 sub get_package {
2627     my ($url, $dest, $session_id)= @_;
2628     if (not defined $session_id) { $session_id = 0; }
2630     my $tpath = dirname($dest);
2631     -d "$tpath" || mkpath "$tpath";
2633     # This is ugly, but I've no time to take a look at "how it works in perl"
2634     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2635         system("gunzip -cd '$dest' > '$dest.in'");
2636         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2637         unlink($dest);
2638         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2639     } else {
2640         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2641     }
2642     return 0;
2646 sub parse_package {
2647     my ($path, $dist, $srv_path, $session_id)= @_;
2648     if (not defined $session_id) { $session_id = 0;}
2649     my ($package, $version, $section, $description);
2650     my $PACKAGES;
2651     my $timestamp = &get_time();
2653     if(not stat("$path.in")) {
2654         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2655         return;
2656     }
2658     open($PACKAGES, "<$path.in");
2659     if(not defined($PACKAGES)) {
2660         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2661         return;
2662     }
2664     # Read lines
2665     while (<$PACKAGES>){
2666         my $line = $_;
2667         # Unify
2668         chop($line);
2670         # Use empty lines as a trigger
2671         if ($line =~ /^\s*$/){
2672             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2673             push(@packages_list_statements, $sql);
2674             $package = "none";
2675             $version = "none";
2676             $section = "none";
2677             $description = "none"; 
2678             next;
2679         }
2681         # Trigger for package name
2682         if ($line =~ /^Package:\s/){
2683             ($package)= ($line =~ /^Package: (.*)$/);
2684             next;
2685         }
2687         # Trigger for version
2688         if ($line =~ /^Version:\s/){
2689             ($version)= ($line =~ /^Version: (.*)$/);
2690             next;
2691         }
2693         # Trigger for description
2694         if ($line =~ /^Description:\s/){
2695             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2696             next;
2697         }
2699         # Trigger for section
2700         if ($line =~ /^Section:\s/){
2701             ($section)= ($line =~ /^Section: (.*)$/);
2702             next;
2703         }
2705         # Trigger for filename
2706         if ($line =~ /^Filename:\s/){
2707             my ($filename) = ($line =~ /^Filename: (.*)$/);
2708             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2709             next;
2710         }
2711     }
2713     close( $PACKAGES );
2714     unlink( "$path.in" );
2715     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2719 sub store_fileinfo {
2720     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2722     my %fileinfo = (
2723         'package' => $package,
2724         'dist' => $dist,
2725         'version' => $vers,
2726     );
2728     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2732 sub cleanup_and_extract {
2733     my $fileinfo = $repo_files{ $File::Find::name };
2735     if( defined $fileinfo ) {
2737         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2738         my $sql;
2739         my $package = $fileinfo->{ 'package' };
2740         my $newver = $fileinfo->{ 'version' };
2742         mkpath($dir);
2743         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2745                 if( -f "$dir/DEBIAN/templates" ) {
2747                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2749                         my $tmpl= "";
2750                         {
2751                                 local $/=undef;
2752                                 open FILE, "$dir/DEBIAN/templates";
2753                                 $tmpl = &encode_base64(<FILE>);
2754                                 close FILE;
2755                         }
2756                         rmtree("$dir/DEBIAN/templates");
2758                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2759                 push @packages_list_statements, $sql;
2760                 }
2761     }
2763     return;
2767 sub register_at_foreign_servers {   
2768     my ($kernel) = $_[KERNEL];
2770     # hole alle bekannten server aus known_server_db
2771     my $server_sql = "SELECT * FROM $known_server_tn";
2772     my $server_res = $known_server_db->exec_statement($server_sql);
2774     # no entries in known_server_db
2775     if (not ref(@$server_res[0]) eq "ARRAY") { 
2776         # TODO
2777     }
2779     # detect already connected clients
2780     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2781     my $client_res = $known_clients_db->exec_statement($client_sql);
2783     # send my server details to all other gosa-si-server within the network
2784     foreach my $hit (@$server_res) {
2785         my $hostname = @$hit[0];
2786         my $hostkey = &create_passwd;
2788         # add already connected clients to registration message 
2789         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2790         &add_content2xml_hash($myhash, 'key', $hostkey);
2791         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2792         
2793         # build registration message and send it
2794         my $foreign_server_msg = &create_xml_string($myhash);
2795         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2796     }
2797     
2798     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2799     return;
2803 #==== MAIN = main ==============================================================
2804 #  parse commandline options
2805 Getopt::Long::Configure( "bundling" );
2806 GetOptions("h|help" => \&usage,
2807         "c|config=s" => \$cfg_file,
2808         "f|foreground" => \$foreground,
2809         "v|verbose+" => \$verbose,
2810         "no-bus+" => \$no_bus,
2811         "no-arp+" => \$no_arp,
2812            );
2814 #  read and set config parameters
2815 &check_cmdline_param ;
2816 &read_configfile;
2817 &check_pid;
2819 $SIG{CHLD} = 'IGNORE';
2821 # forward error messages to logfile
2822 if( ! $foreground ) {
2823   open( STDIN,  '+>/dev/null' );
2824   open( STDOUT, '+>&STDIN'    );
2825   open( STDERR, '+>&STDIN'    );
2828 # Just fork, if we are not in foreground mode
2829 if( ! $foreground ) { 
2830     chdir '/'                 or die "Can't chdir to /: $!";
2831     $pid = fork;
2832     setsid                    or die "Can't start a new session: $!";
2833     umask 0;
2834 } else { 
2835     $pid = $$; 
2838 # Do something useful - put our PID into the pid_file
2839 if( 0 != $pid ) {
2840     open( LOCK_FILE, ">$pid_file" );
2841     print LOCK_FILE "$pid\n";
2842     close( LOCK_FILE );
2843     if( !$foreground ) { 
2844         exit( 0 ) 
2845     };
2848 # parse head url and revision from svn
2849 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2850 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2851 $server_headURL = defined $1 ? $1 : 'unknown' ;
2852 $server_revision = defined $2 ? $2 : 'unknown' ;
2853 if ($server_headURL =~ /\/tag\// || 
2854         $server_headURL =~ /\/branches\// ) {
2855     $server_status = "stable"; 
2856 } else {
2857     $server_status = "developmental" ;
2861 daemon_log(" ", 1);
2862 daemon_log("$0 started!", 1);
2863 daemon_log("status: $server_status", 1);
2864 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2866 if ($no_bus > 0) {
2867     $bus_activ = "false"
2870 # connect to incoming_db
2871 unlink($incoming_file_name);
2872 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2873 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2875 # connect to gosa-si job queue
2876 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2877 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2879 # connect to known_clients_db
2880 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2881 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2883 # connect to foreign_clients_db
2884 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2885 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2887 # connect to known_server_db
2888 unlink($known_server_file_name);
2889 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2890 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2892 # connect to login_usr_db
2893 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2894 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2896 # connect to fai_server_db and fai_release_db
2897 unlink($fai_server_file_name);
2898 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2899 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2901 unlink($fai_release_file_name);
2902 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2903 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2905 # connect to packages_list_db
2906 #unlink($packages_list_file_name);
2907 unlink($packages_list_under_construction);
2908 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2909 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2911 # connect to messaging_db
2912 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2913 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2916 # create xml object used for en/decrypting
2917 $xml = new XML::Simple();
2920 # foreign servers 
2921 my @foreign_server_list;
2923 # add foreign server from cfg file
2924 if ($foreign_server_string ne "") {
2925     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2926     foreach my $foreign_server (@cfg_foreign_server_list) {
2927         push(@foreign_server_list, $foreign_server);
2928     }
2931 # add foreign server from dns
2932 my @tmp_servers;
2933 if ( !$server_domain) {
2934     # Try our DNS Searchlist
2935     for my $domain(get_dns_domains()) {
2936         chomp($domain);
2937         my @tmp_domains= &get_server_addresses($domain);
2938         if(@tmp_domains) {
2939             for my $tmp_server(@tmp_domains) {
2940                 push @tmp_servers, $tmp_server;
2941             }
2942         }
2943     }
2944     if(@tmp_servers && length(@tmp_servers)==0) {
2945         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2946     }
2947 } else {
2948     @tmp_servers = &get_server_addresses($server_domain);
2949     if( 0 == @tmp_servers ) {
2950         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2951     }
2953 foreach my $server (@tmp_servers) { 
2954     unshift(@foreign_server_list, $server); 
2956 # eliminate duplicate entries
2957 @foreign_server_list = &del_doubles(@foreign_server_list);
2958 my $all_foreign_server = join(", ", @foreign_server_list);
2959 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2961 # add all found foreign servers to known_server
2962 my $act_timestamp = &get_time();
2963 foreach my $foreign_server (@foreign_server_list) {
2964     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2965             primkey=>['hostname'],
2966             hostname=>$foreign_server,
2967             status=>'not_jet_registered',
2968             hostkey=>"none",
2969             timestamp=>$act_timestamp,
2970             } );
2974 POE::Component::Server::TCP->new(
2975     Alias => "TCP_SERVER",
2976         Port => $server_port,
2977         ClientInput => sub {
2978         my ($kernel, $input) = @_[KERNEL, ARG0];
2979         push(@tasks, $input);
2980         push(@msgs_to_decrypt, $input);
2981         $kernel->yield("msg_to_decrypt");
2982         },
2983     InlineStates => {
2984         msg_to_decrypt => \&msg_to_decrypt,
2985         next_task => \&next_task,
2986         task_result => \&handle_task_result,
2987         task_done   => \&handle_task_done,
2988         task_debug  => \&handle_task_debug,
2989         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2990     }
2991 );
2993 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2995 # create session for repeatedly checking the job queue for jobs
2996 POE::Session->create(
2997         inline_states => {
2998                 _start => \&session_start,
2999         register_at_foreign_servers => \&register_at_foreign_servers,
3000         sig_handler => \&sig_handler,
3001         next_task => \&next_task,
3002         task_result => \&handle_task_result,
3003         task_done   => \&handle_task_done,
3004         task_debug  => \&handle_task_debug,
3005         watch_for_next_tasks => \&watch_for_next_tasks,
3006         watch_for_new_messages => \&watch_for_new_messages,
3007         watch_for_delivery_messages => \&watch_for_delivery_messages,
3008         watch_for_done_messages => \&watch_for_done_messages,
3009                 watch_for_new_jobs => \&watch_for_new_jobs,
3010         watch_for_done_jobs => \&watch_for_done_jobs,
3011         watch_for_old_known_clients => \&watch_for_old_known_clients,
3012         create_packages_list_db => \&run_create_packages_list_db,
3013         create_fai_server_db => \&run_create_fai_server_db,
3014         create_fai_release_db => \&run_create_fai_release_db,
3015         session_run_result => \&session_run_result,
3016         session_run_debug => \&session_run_debug,
3017         session_run_done => \&session_run_done,
3018         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3019         }
3020 );
3023 # import all modules
3024 &import_modules;
3026 # TODO
3027 # check wether all modules are gosa-si valid passwd check
3031 POE::Kernel->run();
3032 exit;