Code

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