Code

bugfix: gosa-si-server: send_usr_msg does no longer cause a si-server crash
[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;
1057     # check whether this message should be processed here
1058     if ($error == 0) {
1059         $header = @{$msg_hash->{'header'}}[0];
1060         $target = @{$msg_hash->{'target'}}[0];
1061         $source = @{$msg_hash->{'source'}}[0];
1062         my ($target_ip, $target_port) = split(':', $target);
1063                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1064                         my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1065                 }
1067         # target and source is equal to GOSA -> process here
1068         if (not $done) {
1069             if ($target eq "GOSA" && $source eq "GOSA") {
1070                 $done = 1;                    
1071             }
1072         }
1074         # target is own address without forward_to_gosa-tag -> process here
1075         if (not $done) {
1076             if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1077                 $done = 1;
1078                 if ($source eq "GOSA") {
1079                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1080                 }
1081                 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1082             }
1083         }
1085         # target is a client address in known_clients -> process here
1086         if (not $done) {
1087             $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1088             $res = $known_clients_db->select_dbentry($sql);
1089             if (keys(%$res) > 0) {
1090                 $done = 1; 
1091                 my $hostname = $res->{1}->{'hostname'};
1092                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1093                 print STDERR "target is a client address in known_clients -> process here\n";
1094             }
1095         }
1097         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1098         if (not $done) {
1099             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1100             my $gosa_at;
1101             my $gosa_session_id;
1102             if (($target eq $server_address) && (defined $forward_to_gosa)){
1103                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1104                 if ($gosa_at ne $server_address) {
1105                     $done = 1;
1106                     print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1107                 }
1108             }
1109         }
1111         # if message should be processed here -> add message to incoming_db
1112         if ($done) {
1114             # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1115             # so gosa-si-server knows how to process this kind of messages
1116             if ($header =~ /^gosa_/ || $header =~ /job_/) {
1117                 $module = "GosaPackages";
1118             }
1120             my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1121                     primkey=>[],
1122                     headertag=>$header,
1123                     targettag=>$target,
1124                     xmlmessage=>&encode_base64($msg),
1125                     timestamp=>&get_time,
1126                     module=>$module,
1127                     sessionid=>$session_id,
1128                     } );
1130         }
1132         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1133         if (not $done) {
1134             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1135             my $gosa_at;
1136             my $gosa_session_id;
1137             if (($target eq $server_address) && (defined $forward_to_gosa)){
1138                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1139                 if ($gosa_at eq $server_address) {
1140                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1141                     if( defined $session_reference ) {
1142                         $heap = $session_reference->get_heap();
1143                     }
1144                     if(exists $heap->{'client'}) {
1145                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1146                         $heap->{'client'}->put($msg);
1147                     }
1148                     $done = 1;
1149                     print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1150                 }
1151             }
1153         }
1155         # target is a client address in foreign_clients -> forward to registration server
1156         if (not $done) {
1157             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1158             $res = $foreign_clients_db->select_dbentry($sql);
1159             if (keys(%$res) > 0) {
1160                 my $hostname = $res->{1}->{'hostname'};
1161                 my $regserver = $res->{1}->{'regserver'};
1162                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1163                 my $res = $known_server_db->select_dbentry($sql);
1164                 if (keys(%$res) > 0) {
1165                     my $regserver_key = $res->{1}->{'hostkey'};
1166                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1167                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1168                     if ($source eq "GOSA") {
1169                         $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1170                     }
1171                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1172                 }
1173                 $done = 1;
1174                 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1175             }
1176         }
1178         # target is a server address -> forward to server
1179         if (not $done) {
1180             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1181             $res = $known_server_db->select_dbentry($sql);
1182             if (keys(%$res) > 0) {
1183                 my $hostkey = $res->{1}->{'hostkey'};
1185                 if ($source eq "GOSA") {
1186                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1187                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1189                 }
1191                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1192                 $done = 1;
1193                 print STDERR "target is a server address -> forward to server\n";
1194             }
1197         }
1199         if (not $done) {
1200             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1201         }
1202     }
1204     return;
1208 sub next_task {
1209     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1210     my $running_task = POE::Wheel::Run->new(
1211             Program => sub { process_task($session, $heap, $task) },
1212             StdioFilter => POE::Filter::Reference->new(),
1213             StdoutEvent  => "task_result",
1214             StderrEvent  => "task_debug",
1215             CloseEvent   => "task_done",
1216             );
1217     $heap->{task}->{ $running_task->ID } = $running_task;
1220 sub handle_task_result {
1221     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1222     my $client_answer = $result->{'answer'};
1223     if( $client_answer =~ s/session_id=(\d+)$// ) {
1224         my $session_id = $1;
1225         if( defined $session_id ) {
1226             my $session_reference = $kernel->ID_id_to_session($session_id);
1227             if( defined $session_reference ) {
1228                 $heap = $session_reference->get_heap();
1229             }
1230         }
1232         if(exists $heap->{'client'}) {
1233             $heap->{'client'}->put($client_answer);
1234         }
1235     }
1236     $kernel->sig(CHLD => "child_reap");
1239 sub handle_task_debug {
1240     my $result = $_[ARG0];
1241     print STDERR "$result\n";
1244 sub handle_task_done {
1245     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1246     delete $heap->{task}->{$task_id};
1249 sub process_task {
1250     no strict "refs";
1251     my ($session, $heap, $task) = @_;
1252     my $error = 0;
1253     my $answer_l;
1254     my ($answer_header, @answer_target_l, $answer_source);
1255     my $client_answer = "";
1257     # prepare all variables needed to process message
1258     #my $msg = $task->{'xmlmessage'};
1259     my $msg = &decode_base64($task->{'xmlmessage'});
1260     my $incoming_id = $task->{'id'};
1261     my $module = $task->{'module'};
1262     my $header =  $task->{'headertag'};
1263     my $session_id = $task->{'sessionid'};
1264     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1265     my $source = @{$msg_hash->{'source'}}[0];
1266     
1267     # set timestamp of incoming client uptodate, so client will not 
1268     # be deleted from known_clients because of expiration
1269     my $act_time = &get_time();
1270     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1271     my $res = $known_clients_db->exec_statement($sql);
1273     ######################
1274     # process incoming msg
1275     if( $error == 0) {
1276         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1277         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1278         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1280         if ( 0 < @{$answer_l} ) {
1281             my $answer_str = join("\n", @{$answer_l});
1282             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1283                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1284             }
1285             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1286         } else {
1287             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1288         }
1290     }
1291     if( !$answer_l ) { $error++ };
1293     ########
1294     # answer
1295     if( $error == 0 ) {
1297         foreach my $answer ( @{$answer_l} ) {
1298             # check outgoing msg to xml validity
1299             my $answer_hash = &check_outgoing_xml_validity($answer);
1300             if( not defined $answer_hash ) { next; }
1301             
1302             $answer_header = @{$answer_hash->{'header'}}[0];
1303             @answer_target_l = @{$answer_hash->{'target'}};
1304             $answer_source = @{$answer_hash->{'source'}}[0];
1306             # deliver msg to all targets 
1307             foreach my $answer_target ( @answer_target_l ) {
1309                 # targets of msg are all gosa-si-clients in known_clients_db
1310                 if( $answer_target eq "*" ) {
1311                     # answer is for all clients
1312                     my $sql_statement= "SELECT * FROM known_clients";
1313                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1314                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1315                         my $host_name = $hit->{hostname};
1316                         my $host_key = $hit->{hostkey};
1317                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1318                         &update_jobdb_status_for_send_msgs($answer, $error);
1319                     }
1320                 }
1322                 # targets of msg are all gosa-si-server in known_server_db
1323                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1324                     # answer is for all server in known_server
1325                     my $sql_statement= "SELECT * FROM $known_server_tn";
1326                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1327                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1328                         my $host_name = $hit->{hostname};
1329                         my $host_key = $hit->{hostkey};
1330                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1331                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1332                         &update_jobdb_status_for_send_msgs($answer, $error);
1333                     }
1334                 }
1336                 # target of msg is GOsa
1337                                 elsif( $answer_target eq "GOSA" ) {
1338                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1339                                         my $add_on = "";
1340                     if( defined $session_id ) {
1341                         $add_on = ".session_id=$session_id";
1342                     }
1343                     # answer is for GOSA and has to returned to connected client
1344                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1345                     $client_answer = $gosa_answer.$add_on;
1346                 }
1348                 # target of msg is job queue at this host
1349                 elsif( $answer_target eq "JOBDB") {
1350                     $answer =~ /<header>(\S+)<\/header>/;   
1351                     my $header;
1352                     if( defined $1 ) { $header = $1; }
1353                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1354                     &update_jobdb_status_for_send_msgs($answer, $error);
1355                 }
1357                 # target of msg is a mac address
1358                 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 ) {
1359                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1360                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1361                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1362                     my $found_ip_flag = 0;
1363                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1364                         my $host_name = $hit->{hostname};
1365                         my $host_key = $hit->{hostkey};
1366                         $answer =~ s/$answer_target/$host_name/g;
1367                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1368                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1369                         &update_jobdb_status_for_send_msgs($answer, $error);
1370                         $found_ip_flag++ ;
1371                     }   
1372                     if( $found_ip_flag == 0) {
1373                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1374                     }
1376                 #  answer is for one specific host   
1377                 } else {
1378                     # get encrypt_key
1379                     my $encrypt_key = &get_encrypt_key($answer_target);
1380                     if( not defined $encrypt_key ) {
1381                         # unknown target
1382                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1383                         next;
1384                     }
1385                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1386                     &update_jobdb_status_for_send_msgs($answer, $error);
1387                 }
1388             }
1389         }
1390     }
1392     my $filter = POE::Filter::Reference->new();
1393     my %result = ( 
1394             status => "seems ok to me",
1395             answer => $client_answer,
1396             );
1398     my $output = $filter->put( [ \%result ] );
1399     print @$output;
1404 sub session_start {
1405     my ($kernel) = $_[KERNEL];
1406     &trigger_db_loop($kernel);
1407     $global_kernel = $kernel;
1408     $kernel->yield('register_at_foreign_servers');
1409         $kernel->yield('create_fai_server_db', $fai_server_tn );
1410         $kernel->yield('create_fai_release_db', $fai_release_tn );
1411     $kernel->yield('watch_for_next_tasks');
1412         $kernel->sig(USR1 => "sig_handler");
1413         $kernel->sig(USR2 => "create_packages_list_db");
1414         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1415         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1416         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1417     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1418         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1419     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1423 sub trigger_db_loop {
1424         my ($kernel) = @_ ;
1425 #       $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1426 #       $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1427 #       $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1428 #    $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1429 #       $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1430 #    $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1434 sub watch_for_done_jobs {
1435     my ($kernel,$heap) = @_[KERNEL, HEAP];
1437     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1438         my $res = $job_db->select_dbentry( $sql_statement );
1440     while( my ($id, $hit) = each %{$res} ) {
1441         my $jobdb_id = $hit->{id};
1442         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1443         my $res = $job_db->del_dbentry($sql_statement); 
1444     }
1446     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1450 sub watch_for_new_jobs {
1451         if($watch_for_new_jobs_in_progress == 0) {
1452                 $watch_for_new_jobs_in_progress = 1;
1453                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1455                 # check gosa job queue for jobs with executable timestamp
1456                 my $timestamp = &get_time();
1457                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1458                 my $res = $job_db->exec_statement( $sql_statement );
1460                 # Merge all new jobs that would do the same actions
1461                 my @drops;
1462                 my $hits;
1463                 foreach my $hit (reverse @{$res} ) {
1464                         my $macaddress= lc @{$hit}[8];
1465                         my $headertag= @{$hit}[5];
1466                         if(
1467                                 defined($hits->{$macaddress}) &&
1468                                 defined($hits->{$macaddress}->{$headertag}) &&
1469                                 defined($hits->{$macaddress}->{$headertag}[0])
1470                         ) {
1471                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1472                         }
1473                         $hits->{$macaddress}->{$headertag}= $hit;
1474                 }
1476                 # Delete new jobs with a matching job in state 'processing'
1477                 foreach my $macaddress (keys %{$hits}) {
1478                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1479                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1480                                 if(defined($jobdb_id)) {
1481                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1482                                         my $res = $job_db->exec_statement( $sql_statement );
1483                                         foreach my $hit (@{$res}) {
1484                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1485                                         }
1486                                 } else {
1487                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1488                                 }
1489                         }
1490                 }
1492                 # Commit deletion
1493                 $job_db->exec_statementlist(\@drops);
1495                 # Look for new jobs that could be executed
1496                 foreach my $macaddress (keys %{$hits}) {
1498                         # Look if there is an executing job
1499                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1500                         my $res = $job_db->exec_statement( $sql_statement );
1502                         # Skip new jobs for host if there is a processing job
1503                         if(defined($res) and defined @{$res}[0]) {
1504                                 next;
1505                         }
1507                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1508                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1509                                 if(defined($jobdb_id)) {
1510                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1512                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1513                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1514                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1516                                         # expect macaddress is unique!!!!!!
1517                                         my $target = $res_hash->{1}->{hostname};
1519                                         # change header
1520                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1522                                         # add sqlite_id
1523                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1525                                         $job_msg =~ /<header>(\S+)<\/header>/;
1526                                         my $header = $1 ;
1527                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1529                                         # update status in job queue to 'processing'
1530                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1531                                         my $res = $job_db->update_dbentry($sql_statement);
1532 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1534                                         # We don't want parallel processing
1535                                         last;
1536                                 }
1537                         }
1538                 }
1540                 $watch_for_new_jobs_in_progress = 0;
1541                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1542         }
1546 sub watch_for_new_messages {
1547     my ($kernel,$heap) = @_[KERNEL, HEAP];
1548     my @coll_user_msg;   # collection list of outgoing messages
1549     
1550     # check messaging_db for new incoming messages with executable timestamp
1551     my $timestamp = &get_time();
1552     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1553     my $res = $messaging_db->exec_statement( $sql_statement );
1554         foreach my $hit (@{$res}) {
1556         # create outgoing messages
1557         my $message_to = @{$hit}[3];
1558         # translate message_to to plain login name
1559         my @message_to_l = split(/,/, $message_to);  
1560                 my %receiver_h; 
1561                 foreach my $receiver (@message_to_l) {
1562                         if ($receiver =~ /^u_([\s\S]*)$/) {
1563                                 $receiver_h{$1} = 0;
1564                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1565                                 my $group_name = $1;
1566                                 # fetch all group members from ldap and add them to receiver hash
1567                                 my $ldap_handle = &get_ldap_handle();
1568                                 if (defined $ldap_handle) {
1569                                                 my $mesg = $ldap_handle->search(
1570                                                                                 base => $ldap_base,
1571                                                                                 scope => 'sub',
1572                                                                                 attrs => ['memberUid'],
1573                                                                                 filter => "cn=$group_name",
1574                                                                                 );
1575                                                 if ($mesg->count) {
1576                                                                 my @entries = $mesg->entries;
1577                                                                 foreach my $entry (@entries) {
1578                                                                                 my @receivers= $entry->get_value("memberUid");
1579                                                                                 foreach my $receiver (@receivers) { 
1580                                                                                                 $receiver_h{$1} = 0;
1581                                                                                 }
1582                                                                 }
1583                                                 } 
1584                                                 # translating errors ?
1585                                                 if ($mesg->code) {
1586                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1587                                                 }
1588                                 # ldap handle error ?           
1589                                 } else {
1590                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1591                                 }
1592                         } else {
1593                                 my $sbjct = &encode_base64(@{$hit}[1]);
1594                                 my $msg = &encode_base64(@{$hit}[7]);
1595                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1596                         }
1597                 }
1598                 my @receiver_l = keys(%receiver_h);
1600         my $message_id = @{$hit}[0];
1602         #add each outgoing msg to messaging_db
1603         my $receiver;
1604         foreach $receiver (@receiver_l) {
1605             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1606                 "VALUES ('".
1607                 $message_id."', '".    # id
1608                 @{$hit}[1]."', '".     # subject
1609                 @{$hit}[2]."', '".     # message_from
1610                 $receiver."', '".      # message_to
1611                 "none"."', '".         # flag
1612                 "out"."', '".          # direction
1613                 @{$hit}[6]."', '".     # delivery_time
1614                 @{$hit}[7]."', '".     # message
1615                 $timestamp."'".     # timestamp
1616                 ")";
1617             &daemon_log("M DEBUG: $sql_statement", 1);
1618             my $res = $messaging_db->exec_statement($sql_statement);
1619             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1620         }
1622         # set incoming message to flag d=deliverd
1623         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1624         &daemon_log("M DEBUG: $sql_statement", 7);
1625         $res = $messaging_db->update_dbentry($sql_statement);
1626         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1627     }
1629     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1630     return;
1633 sub watch_for_delivery_messages {
1634     my ($kernel, $heap) = @_[KERNEL, HEAP];
1636     # select outgoing messages
1637     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1638     #&daemon_log("0 DEBUG: $sql", 7);
1639     my $res = $messaging_db->exec_statement( $sql_statement );
1640     
1641     # build out msg for each    usr
1642     foreach my $hit (@{$res}) {
1643         my $receiver = @{$hit}[3];
1644         my $msg_id = @{$hit}[0];
1645         my $subject = @{$hit}[1];
1646         my $message = @{$hit}[7];
1648         # resolve usr -> host where usr is logged in
1649         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1650         #&daemon_log("0 DEBUG: $sql", 7);
1651         my $res = $login_users_db->exec_statement($sql);
1653         # reciver is logged in nowhere
1654         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1656                 my $send_succeed = 0;
1657                 foreach my $hit (@$res) {
1658                                 my $receiver_host = @$hit[0];
1659                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1661                                 # fetch key to encrypt msg propperly for usr/host
1662                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1663                                 &daemon_log("0 DEBUG: $sql", 7);
1664                                 my $res = $known_clients_db->exec_statement($sql);
1666                                 # host is already down
1667                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1669                                 # host is on
1670                                 my $receiver_key = @{@{$res}[0]}[2];
1671                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1672                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1673                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1674                                 if ($error == 0 ) {
1675                                         $send_succeed++ ;
1676                                 }
1677                 }
1679                 if ($send_succeed) {
1680                                 # set outgoing msg at db to deliverd
1681                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1682                                 &daemon_log("0 DEBUG: $sql", 7);
1683                                 my $res = $messaging_db->exec_statement($sql); 
1684                 }
1685         }
1687     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1688     return;
1692 sub watch_for_done_messages {
1693     my ($kernel,$heap) = @_[KERNEL, HEAP];
1695     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1696     #&daemon_log("0 DEBUG: $sql", 7);
1697     my $res = $messaging_db->exec_statement($sql); 
1699     foreach my $hit (@{$res}) {
1700         my $msg_id = @{$hit}[0];
1702         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1703         #&daemon_log("0 DEBUG: $sql", 7); 
1704         my $res = $messaging_db->exec_statement($sql);
1706         # not all usr msgs have been seen till now
1707         if ( ref(@$res[0]) eq "ARRAY") { next; }
1708         
1709         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1710         #&daemon_log("0 DEBUG: $sql", 7);
1711         $res = $messaging_db->exec_statement($sql);
1712     
1713     }
1715     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1716     return;
1720 sub watch_for_old_known_clients {
1721     my ($kernel,$heap) = @_[KERNEL, HEAP];
1723     my $sql_statement = "SELECT * FROM $known_clients_tn";
1724     my $res = $known_clients_db->select_dbentry( $sql_statement );
1726     my $act_time = int(&get_time());
1728     while ( my ($hit_num, $hit) = each %$res) {
1729         my $expired_timestamp = int($hit->{'timestamp'});
1730         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1731         my $dt = DateTime->new( year   => $1,
1732                 month  => $2,
1733                 day    => $3,
1734                 hour   => $4,
1735                 minute => $5,
1736                 second => $6,
1737                 );
1739         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1740         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1741         if ($act_time > $expired_timestamp) {
1742             my $hostname = $hit->{'hostname'};
1743             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1744             my $del_res = $known_clients_db->exec_statement($del_sql);
1746             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1747         }
1749     }
1751     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1755 sub watch_for_next_tasks {
1756     my ($kernel,$heap) = @_[KERNEL, HEAP];
1758     my $sql = "SELECT * FROM $incoming_tn";
1759     my $res = $incoming_db->select_dbentry($sql);
1761     while ( my ($hit_num, $hit) = each %$res) {
1762         my $headertag = $hit->{'headertag'};
1763         if ($headertag =~ /^answer_(\d+)/) {
1764             # do not start processing, this message is for a still running POE::Wheel
1765             next;
1766         }
1767         my $message_id = $hit->{'id'};
1768         $kernel->yield('next_task', $hit);
1770         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1771         my $res = $incoming_db->exec_statement($sql);
1772     }
1774     $kernel->delay_set('watch_for_next_tasks', 1); 
1778 sub get_ldap_handle {
1779         my ($session_id) = @_;
1780         my $heap;
1781         my $ldap_handle;
1783         if (not defined $session_id ) { $session_id = 0 };
1784         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1786         if ($session_id == 0) {
1787                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1788                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1789                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1791         } else {
1792                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1793                 if( defined $session_reference ) {
1794                         $heap = $session_reference->get_heap();
1795                 }
1797                 if (not defined $heap) {
1798                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1799                         return;
1800                 }
1802                 # TODO: This "if" is nonsense, because it doesn't prove that the
1803                 #       used handle is still valid - or if we've to reconnect...
1804                 #if (not exists $heap->{ldap_handle}) {
1805                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1806                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1807                         $heap->{ldap_handle} = $ldap_handle;
1808                 #}
1809         }
1810         return $ldap_handle;
1814 sub change_fai_state {
1815     my ($st, $targets, $session_id) = @_;
1816     $session_id = 0 if not defined $session_id;
1817     # Set FAI state to localboot
1818     my %mapActions= (
1819         reboot    => '',
1820         update    => 'softupdate',
1821         localboot => 'localboot',
1822         reinstall => 'install',
1823         rescan    => '',
1824         wake      => '',
1825         memcheck  => 'memcheck',
1826         sysinfo   => 'sysinfo',
1827         install   => 'install',
1828     );
1830     # Return if this is unknown
1831     if (!exists $mapActions{ $st }){
1832         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1833       return;
1834     }
1836     my $state= $mapActions{ $st };
1838     my $ldap_handle = &get_ldap_handle($session_id);
1839     if( defined($ldap_handle) ) {
1841       # Build search filter for hosts
1842         my $search= "(&(objectClass=GOhard)";
1843         foreach (@{$targets}){
1844             $search.= "(macAddress=$_)";
1845         }
1846         $search.= ")";
1848       # If there's any host inside of the search string, procress them
1849         if (!($search =~ /macAddress/)){
1850             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1851             return;
1852         }
1854       # Perform search for Unit Tag
1855       my $mesg = $ldap_handle->search(
1856           base   => $ldap_base,
1857           scope  => 'sub',
1858           attrs  => ['dn', 'FAIstate', 'objectClass'],
1859           filter => "$search"
1860           );
1862           if ($mesg->count) {
1863                   my @entries = $mesg->entries;
1864                   if (0 == @entries) {
1865                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
1866                   }
1868                   foreach my $entry (@entries) {
1869                           # Only modify entry if it is not set to '$state'
1870                           if ($entry->get_value("FAIstate") ne "$state"){
1871                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1872                                   my $result;
1873                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1874                                   if (exists $tmp{'FAIobject'}){
1875                                           if ($state eq ''){
1876                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1877                                                           delete => [ FAIstate => [] ] ]);
1878                                           } else {
1879                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1880                                                           replace => [ FAIstate => $state ] ]);
1881                                           }
1882                                   } elsif ($state ne ''){
1883                                           $result= $ldap_handle->modify($entry->dn, changes => [
1884                                                   add     => [ objectClass => 'FAIobject' ],
1885                                                   add     => [ FAIstate => $state ] ]);
1886                                   }
1888                                   # Errors?
1889                                   if ($result->code){
1890                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1891                                   }
1892                           } else {
1893                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1894                           }  
1895                   }
1896           } else {
1897                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1898           }
1900     # if no ldap handle defined
1901     } else {
1902         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1903     }
1905         return;
1909 sub change_goto_state {
1910     my ($st, $targets, $session_id) = @_;
1911     $session_id = 0  if not defined $session_id;
1913     # Switch on or off?
1914     my $state= $st eq 'active' ? 'active': 'locked';
1916     my $ldap_handle = &get_ldap_handle($session_id);
1917     if( defined($ldap_handle) ) {
1919       # Build search filter for hosts
1920       my $search= "(&(objectClass=GOhard)";
1921       foreach (@{$targets}){
1922         $search.= "(macAddress=$_)";
1923       }
1924       $search.= ")";
1926       # If there's any host inside of the search string, procress them
1927       if (!($search =~ /macAddress/)){
1928         return;
1929       }
1931       # Perform search for Unit Tag
1932       my $mesg = $ldap_handle->search(
1933           base   => $ldap_base,
1934           scope  => 'sub',
1935           attrs  => ['dn', 'gotoMode'],
1936           filter => "$search"
1937           );
1939       if ($mesg->count) {
1940         my @entries = $mesg->entries;
1941         foreach my $entry (@entries) {
1943           # Only modify entry if it is not set to '$state'
1944           if ($entry->get_value("gotoMode") ne $state){
1946             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1947             my $result;
1948             $result= $ldap_handle->modify($entry->dn, changes => [
1949                                                 replace => [ gotoMode => $state ] ]);
1951             # Errors?
1952             if ($result->code){
1953               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1954             }
1956           }
1957         }
1958       } else {
1959                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1960           }
1962     }
1966 sub run_create_fai_server_db {
1967     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1968     my $session_id = $session->ID;
1969     my $task = POE::Wheel::Run->new(
1970             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1971             StdoutEvent  => "session_run_result",
1972             StderrEvent  => "session_run_debug",
1973             CloseEvent   => "session_run_done",
1974             );
1976     $heap->{task}->{ $task->ID } = $task;
1977     return;
1981 sub create_fai_server_db {
1982     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1983         my $result;
1985         if (not defined $session_id) { $session_id = 0; }
1986     my $ldap_handle = &get_ldap_handle();
1987         if(defined($ldap_handle)) {
1988                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1989                 my $mesg= $ldap_handle->search(
1990                         base   => $ldap_base,
1991                         scope  => 'sub',
1992                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1993                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1994                 );
1995                 if($mesg->{'resultCode'} == 0 &&
1996                    $mesg->count != 0) {
1997                    foreach my $entry (@{$mesg->{entries}}) {
1998                            if($entry->exists('FAIrepository')) {
1999                                    # Add an entry for each Repository configured for server
2000                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2001                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2002                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2003                                                    $result= $fai_server_db->add_dbentry( { 
2004                                                                    table => $table_name,
2005                                                                    primkey => ['server', 'release', 'tag'],
2006                                                                    server => $tmp_url,
2007                                                                    release => $tmp_release,
2008                                                                    sections => $tmp_sections,
2009                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2010                                                            } );
2011                                            }
2012                                    }
2013                            }
2014                    }
2015                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2017                 # TODO: Find a way to post the 'create_packages_list_db' event
2018                 if(not defined($dont_create_packages_list)) {
2019                         &create_packages_list_db(undef, undef, $session_id);
2020                 }
2021         }       
2022     
2023     $ldap_handle->disconnect;
2024         return $result;
2028 sub run_create_fai_release_db {
2029     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2030         my $session_id = $session->ID;
2031     my $task = POE::Wheel::Run->new(
2032             Program => sub { &create_fai_release_db($table_name, $session_id) },
2033             StdoutEvent  => "session_run_result",
2034             StderrEvent  => "session_run_debug",
2035             CloseEvent   => "session_run_done",
2036             );
2038     $heap->{task}->{ $task->ID } = $task;
2039     return;
2043 sub create_fai_release_db {
2044         my ($table_name, $session_id) = @_;
2045         my $result;
2047     # used for logging
2048     if (not defined $session_id) { $session_id = 0; }
2050     my $ldap_handle = &get_ldap_handle();
2051         if(defined($ldap_handle)) {
2052                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2053                 my $mesg= $ldap_handle->search(
2054                         base   => $ldap_base,
2055                         scope  => 'sub',
2056                         attrs  => [],
2057                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2058                 );
2059                 if($mesg->{'resultCode'} == 0 &&
2060                         $mesg->count != 0) {
2061                         # Walk through all possible FAI container ou's
2062                         my @sql_list;
2063                         my $timestamp= &get_time();
2064                         foreach my $ou (@{$mesg->{entries}}) {
2065                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2066                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2067                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2068                                         if(@tmp_array) {
2069                                                 foreach my $entry (@tmp_array) {
2070                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2071                                                                 my $sql= 
2072                                                                 "INSERT INTO $table_name "
2073                                                                 ."(timestamp, release, class, type, state) VALUES ("
2074                                                                 .$timestamp.","
2075                                                                 ."'".$entry->{'release'}."',"
2076                                                                 ."'".$entry->{'class'}."',"
2077                                                                 ."'".$entry->{'type'}."',"
2078                                                                 ."'".$entry->{'state'}."')";
2079                                                                 push @sql_list, $sql;
2080                                                         }
2081                                                 }
2082                                         }
2083                                 }
2084                         }
2086                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2087                         if(@sql_list) {
2088                                 unshift @sql_list, "VACUUM";
2089                                 unshift @sql_list, "DELETE FROM $table_name";
2090                                 $fai_release_db->exec_statementlist(\@sql_list);
2091                         }
2092                         daemon_log("$session_id DEBUG: Done with inserting",7);
2093                 }
2094                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2095         }
2096     $ldap_handle->disconnect;
2097         return $result;
2100 sub get_fai_types {
2101         my $tmp_classes = shift || return undef;
2102         my @result;
2104         foreach my $type(keys %{$tmp_classes}) {
2105                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2106                         my $entry = {
2107                                 type => $type,
2108                                 state => $tmp_classes->{$type}[0],
2109                         };
2110                         push @result, $entry;
2111                 }
2112         }
2114         return @result;
2117 sub get_fai_state {
2118         my $result = "";
2119         my $tmp_classes = shift || return $result;
2121         foreach my $type(keys %{$tmp_classes}) {
2122                 if(defined($tmp_classes->{$type}[0])) {
2123                         $result = $tmp_classes->{$type}[0];
2124                         
2125                 # State is equal for all types in class
2126                         last;
2127                 }
2128         }
2130         return $result;
2133 sub resolve_fai_classes {
2134         my ($fai_base, $ldap_handle, $session_id) = @_;
2135         if (not defined $session_id) { $session_id = 0; }
2136         my $result;
2137         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2138         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2139         my $fai_classes;
2141         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2142         my $mesg= $ldap_handle->search(
2143                 base   => $fai_base,
2144                 scope  => 'sub',
2145                 attrs  => ['cn','objectClass','FAIstate'],
2146                 filter => $fai_filter,
2147         );
2148         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2150         if($mesg->{'resultCode'} == 0 &&
2151                 $mesg->count != 0) {
2152                 foreach my $entry (@{$mesg->{entries}}) {
2153                         if($entry->exists('cn')) {
2154                                 my $tmp_dn= $entry->dn();
2156                                 # Skip classname and ou dn parts for class
2157                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2159                                 # Skip classes without releases
2160                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2161                                         next;
2162                                 }
2164                                 my $tmp_cn= $entry->get_value('cn');
2165                                 my $tmp_state= $entry->get_value('FAIstate');
2167                                 my $tmp_type;
2168                                 # Get FAI type
2169                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2170                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2171                                                 $tmp_type= $oclass;
2172                                                 last;
2173                                         }
2174                                 }
2176                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2177                                         # A Subrelease
2178                                         my @sub_releases = split(/,/, $tmp_release);
2180                                         # Walk through subreleases and build hash tree
2181                                         my $hash;
2182                                         while(my $tmp_sub_release = pop @sub_releases) {
2183                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2184                                         }
2185                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2186                                 } else {
2187                                         # A branch, no subrelease
2188                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2189                                 }
2190                         } elsif (!$entry->exists('cn')) {
2191                                 my $tmp_dn= $entry->dn();
2192                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2194                                 # Skip classes without releases
2195                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2196                                         next;
2197                                 }
2199                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2200                                         # A Subrelease
2201                                         my @sub_releases= split(/,/, $tmp_release);
2203                                         # Walk through subreleases and build hash tree
2204                                         my $hash;
2205                                         while(my $tmp_sub_release = pop @sub_releases) {
2206                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2207                                         }
2208                                         # Remove the last two characters
2209                                         chop($hash);
2210                                         chop($hash);
2212                                         eval('$fai_classes->'.$hash.'= {}');
2213                                 } else {
2214                                         # A branch, no subrelease
2215                                         if(!exists($fai_classes->{$tmp_release})) {
2216                                                 $fai_classes->{$tmp_release} = {};
2217                                         }
2218                                 }
2219                         }
2220                 }
2222                 # The hash is complete, now we can honor the copy-on-write based missing entries
2223                 foreach my $release (keys %$fai_classes) {
2224                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2225                 }
2226         }
2227         return $result;
2230 sub apply_fai_inheritance {
2231        my $fai_classes = shift || return {};
2232        my $tmp_classes;
2234        # Get the classes from the branch
2235        foreach my $class (keys %{$fai_classes}) {
2236                # Skip subreleases
2237                if($class =~ /^ou=.*$/) {
2238                        next;
2239                } else {
2240                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2241                }
2242        }
2244        # Apply to each subrelease
2245        foreach my $subrelease (keys %{$fai_classes}) {
2246                if($subrelease =~ /ou=/) {
2247                        foreach my $tmp_class (keys %{$tmp_classes}) {
2248                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2249                                        $fai_classes->{$subrelease}->{$tmp_class} =
2250                                        deep_copy($tmp_classes->{$tmp_class});
2251                                } else {
2252                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2253                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2254                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2255                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2256                                                }
2257                                        }
2258                                }
2259                        }
2260                }
2261        }
2263        # Find subreleases in deeper levels
2264        foreach my $subrelease (keys %{$fai_classes}) {
2265                if($subrelease =~ /ou=/) {
2266                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2267                                if($subsubrelease =~ /ou=/) {
2268                                        apply_fai_inheritance($fai_classes->{$subrelease});
2269                                }
2270                        }
2271                }
2272        }
2274        return $fai_classes;
2277 sub get_fai_release_entries {
2278         my $tmp_classes = shift || return;
2279         my $parent = shift || "";
2280         my @result = shift || ();
2282         foreach my $entry (keys %{$tmp_classes}) {
2283                 if(defined($entry)) {
2284                         if($entry =~ /^ou=.*$/) {
2285                                 my $release_name = $entry;
2286                                 $release_name =~ s/ou=//g;
2287                                 if(length($parent)>0) {
2288                                         $release_name = $parent."/".$release_name;
2289                                 }
2290                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2291                                 foreach my $bufentry(@bufentries) {
2292                                         push @result, $bufentry;
2293                                 }
2294                         } else {
2295                                 my @types = get_fai_types($tmp_classes->{$entry});
2296                                 foreach my $type (@types) {
2297                                         push @result, 
2298                                         {
2299                                                 'class' => $entry,
2300                                                 'type' => $type->{'type'},
2301                                                 'release' => $parent,
2302                                                 'state' => $type->{'state'},
2303                                         };
2304                                 }
2305                         }
2306                 }
2307         }
2309         return @result;
2312 sub deep_copy {
2313         my $this = shift;
2314         if (not ref $this) {
2315                 $this;
2316         } elsif (ref $this eq "ARRAY") {
2317                 [map deep_copy($_), @$this];
2318         } elsif (ref $this eq "HASH") {
2319                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2320         } else { die "what type is $_?" }
2324 sub session_run_result {
2325     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2326     $kernel->sig(CHLD => "child_reap");
2329 sub session_run_debug {
2330     my $result = $_[ARG0];
2331     print STDERR "$result\n";
2334 sub session_run_done {
2335     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2336     delete $heap->{task}->{$task_id};
2340 sub create_sources_list {
2341         my $session_id = shift;
2342         my $ldap_handle = &main::get_ldap_handle;
2343         my $result="/tmp/gosa_si_tmp_sources_list";
2345         # Remove old file
2346         if(stat($result)) {
2347                 unlink($result);
2348                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2349         }
2351         my $fh;
2352         open($fh, ">$result");
2353         if (not defined $fh) {
2354                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2355                 return undef;
2356         }
2357         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2358                 my $mesg=$ldap_handle->search(
2359                         base    => $main::ldap_server_dn,
2360                         scope   => 'base',
2361                         attrs   => 'FAIrepository',
2362                         filter  => 'objectClass=FAIrepositoryServer'
2363                 );
2364                 if($mesg->count) {
2365                         foreach my $entry(@{$mesg->{'entries'}}) {
2366                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2367                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2368                                         my $line = "deb $server $release";
2369                                         $sections =~ s/,/ /g;
2370                                         $line.= " $sections";
2371                                         print $fh $line."\n";
2372                                 }
2373                         }
2374                 }
2375         } else {
2376                 if (defined $main::ldap_server_dn){
2377                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2378                 } else {
2379                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2380                 }
2381         }
2382         close($fh);
2384         return $result;
2388 sub run_create_packages_list_db {
2389     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2390         my $session_id = $session->ID;
2392         my $task = POE::Wheel::Run->new(
2393                                         Priority => +20,
2394                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2395                                         StdoutEvent  => "session_run_result",
2396                                         StderrEvent  => "session_run_debug",
2397                                         CloseEvent   => "session_run_done",
2398                                         );
2399         $heap->{task}->{ $task->ID } = $task;
2403 sub create_packages_list_db {
2404         my ($ldap_handle, $sources_file, $session_id) = @_;
2405         
2406         # it should not be possible to trigger a recreation of packages_list_db
2407         # while packages_list_db is under construction, so set flag packages_list_under_construction
2408         # which is tested befor recreation can be started
2409         if (-r $packages_list_under_construction) {
2410                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2411                 return;
2412         } else {
2413                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2414                 # set packages_list_under_construction to true
2415                 system("touch $packages_list_under_construction");
2416                 @packages_list_statements=();
2417         }
2419         if (not defined $session_id) { $session_id = 0; }
2420         if (not defined $ldap_handle) { 
2421                 $ldap_handle= &get_ldap_handle();
2423                 if (not defined $ldap_handle) {
2424                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2425                         unlink($packages_list_under_construction);
2426                         return;
2427                 }
2428         }
2429         if (not defined $sources_file) { 
2430                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2431                 $sources_file = &create_sources_list($session_id);
2432         }
2434         if (not defined $sources_file) {
2435                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2436                 unlink($packages_list_under_construction);
2437                 return;
2438         }
2440         my $line;
2442         open(CONFIG, "<$sources_file") or do {
2443                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2444                 unlink($packages_list_under_construction);
2445                 return;
2446         };
2448         # Read lines
2449         while ($line = <CONFIG>){
2450                 # Unify
2451                 chop($line);
2452                 $line =~ s/^\s+//;
2453                 $line =~ s/^\s+/ /;
2455                 # Strip comments
2456                 $line =~ s/#.*$//g;
2458                 # Skip empty lines
2459                 if ($line =~ /^\s*$/){
2460                         next;
2461                 }
2463                 # Interpret deb line
2464                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2465                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2466                         my $section;
2467                         foreach $section (split(' ', $sections)){
2468                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2469                         }
2470                 }
2471         }
2473         close (CONFIG);
2475         find(\&cleanup_and_extract, keys( %repo_dirs ));
2476         &main::strip_packages_list_statements();
2477         unshift @packages_list_statements, "VACUUM";
2478         $packages_list_db->exec_statementlist(\@packages_list_statements);
2479         unlink($packages_list_under_construction);
2480         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2481         return;
2484 # This function should do some intensive task to minimize the db-traffic
2485 sub strip_packages_list_statements {
2486     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2487         my @new_statement_list=();
2488         my $hash;
2489         my $insert_hash;
2490         my $update_hash;
2491         my $delete_hash;
2492         my $local_timestamp=get_time();
2494         foreach my $existing_entry (@existing_entries) {
2495                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2496         }
2498         foreach my $statement (@packages_list_statements) {
2499                 if($statement =~ /^INSERT/i) {
2500                         # Assign the values from the insert statement
2501                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2502                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2503                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2504                                 # If section or description has changed, update the DB
2505                                 if( 
2506                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2507                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2508                                 ) {
2509                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2510                                 }
2511                         } else {
2512                                 # Insert a non-existing entry to db
2513                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2514                         }
2515                 } elsif ($statement =~ /^UPDATE/i) {
2516                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2517                         /^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;
2518                         foreach my $distribution (keys %{$hash}) {
2519                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2520                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2521                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2522                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2523                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2524                                                 my $section;
2525                                                 my $description;
2526                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2527                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2528                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2529                                                 }
2530                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2531                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2532                                                 }
2533                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2534                                         }
2535                                 }
2536                         }
2537                 }
2538         }
2540         # TODO: Check for orphaned entries
2542         # unroll the insert_hash
2543         foreach my $distribution (keys %{$insert_hash}) {
2544                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2545                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2546                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2547                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2548                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2549                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2550                                 ."'$local_timestamp')";
2551                         }
2552                 }
2553         }
2555         # unroll the update hash
2556         foreach my $distribution (keys %{$update_hash}) {
2557                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2558                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2559                                 my $set = "";
2560                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2561                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2562                                 }
2563                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2564                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2565                                 }
2566                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2567                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2568                                 }
2569                                 if(defined($set) and length($set) > 0) {
2570                                         $set .= "timestamp = '$local_timestamp'";
2571                                 } else {
2572                                         next;
2573                                 }
2574                                 push @new_statement_list, 
2575                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2576                                         ." distribution = '$distribution'"
2577                                         ." AND package = '$package'"
2578                                         ." AND version = '$version'";
2579                         }
2580                 }
2581         }
2583         @packages_list_statements = @new_statement_list;
2587 sub parse_package_info {
2588     my ($baseurl, $dist, $section, $session_id)= @_;
2589     my ($package);
2590     if (not defined $session_id) { $session_id = 0; }
2591     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2592     $repo_dirs{ "${repo_path}/pool" } = 1;
2594     foreach $package ("Packages.gz"){
2595         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2596         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2597         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2598     }
2599     
2603 sub get_package {
2604     my ($url, $dest, $session_id)= @_;
2605     if (not defined $session_id) { $session_id = 0; }
2607     my $tpath = dirname($dest);
2608     -d "$tpath" || mkpath "$tpath";
2610     # This is ugly, but I've no time to take a look at "how it works in perl"
2611     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2612         system("gunzip -cd '$dest' > '$dest.in'");
2613         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2614         unlink($dest);
2615         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2616     } else {
2617         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2618     }
2619     return 0;
2623 sub parse_package {
2624     my ($path, $dist, $srv_path, $session_id)= @_;
2625     if (not defined $session_id) { $session_id = 0;}
2626     my ($package, $version, $section, $description);
2627     my $PACKAGES;
2628     my $timestamp = &get_time();
2630     if(not stat("$path.in")) {
2631         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2632         return;
2633     }
2635     open($PACKAGES, "<$path.in");
2636     if(not defined($PACKAGES)) {
2637         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2638         return;
2639     }
2641     # Read lines
2642     while (<$PACKAGES>){
2643         my $line = $_;
2644         # Unify
2645         chop($line);
2647         # Use empty lines as a trigger
2648         if ($line =~ /^\s*$/){
2649             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2650             push(@packages_list_statements, $sql);
2651             $package = "none";
2652             $version = "none";
2653             $section = "none";
2654             $description = "none"; 
2655             next;
2656         }
2658         # Trigger for package name
2659         if ($line =~ /^Package:\s/){
2660             ($package)= ($line =~ /^Package: (.*)$/);
2661             next;
2662         }
2664         # Trigger for version
2665         if ($line =~ /^Version:\s/){
2666             ($version)= ($line =~ /^Version: (.*)$/);
2667             next;
2668         }
2670         # Trigger for description
2671         if ($line =~ /^Description:\s/){
2672             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2673             next;
2674         }
2676         # Trigger for section
2677         if ($line =~ /^Section:\s/){
2678             ($section)= ($line =~ /^Section: (.*)$/);
2679             next;
2680         }
2682         # Trigger for filename
2683         if ($line =~ /^Filename:\s/){
2684             my ($filename) = ($line =~ /^Filename: (.*)$/);
2685             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2686             next;
2687         }
2688     }
2690     close( $PACKAGES );
2691     unlink( "$path.in" );
2692     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2696 sub store_fileinfo {
2697     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2699     my %fileinfo = (
2700         'package' => $package,
2701         'dist' => $dist,
2702         'version' => $vers,
2703     );
2705     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2709 sub cleanup_and_extract {
2710     my $fileinfo = $repo_files{ $File::Find::name };
2712     if( defined $fileinfo ) {
2714         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2715         my $sql;
2716         my $package = $fileinfo->{ 'package' };
2717         my $newver = $fileinfo->{ 'version' };
2719         mkpath($dir);
2720         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2722                 if( -f "$dir/DEBIAN/templates" ) {
2724                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2726                         my $tmpl= "";
2727                         {
2728                                 local $/=undef;
2729                                 open FILE, "$dir/DEBIAN/templates";
2730                                 $tmpl = &encode_base64(<FILE>);
2731                                 close FILE;
2732                         }
2733                         rmtree("$dir/DEBIAN/templates");
2735                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2736                 push @packages_list_statements, $sql;
2737                 }
2738     }
2740     return;
2744 sub register_at_foreign_servers {   
2745     my ($kernel) = $_[KERNEL];
2747     # hole alle bekannten server aus known_server_db
2748     my $server_sql = "SELECT * FROM $known_server_tn";
2749     my $server_res = $known_server_db->exec_statement($server_sql);
2751     # no entries in known_server_db
2752     if (not ref(@$server_res[0]) eq "ARRAY") { 
2753         # TODO
2754     }
2756     # detect already connected clients
2757     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2758     my $client_res = $known_clients_db->exec_statement($client_sql);
2760     # send my server details to all other gosa-si-server within the network
2761     foreach my $hit (@$server_res) {
2762         my $hostname = @$hit[0];
2763         my $hostkey = &create_passwd;
2765         # add already connected clients to registration message 
2766         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2767         &add_content2xml_hash($myhash, 'key', $hostkey);
2768         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2769         
2770         # build registration message and send it
2771         my $foreign_server_msg = &create_xml_string($myhash);
2772         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2773     }
2774     
2775     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2776     return;
2780 #==== MAIN = main ==============================================================
2781 #  parse commandline options
2782 Getopt::Long::Configure( "bundling" );
2783 GetOptions("h|help" => \&usage,
2784         "c|config=s" => \$cfg_file,
2785         "f|foreground" => \$foreground,
2786         "v|verbose+" => \$verbose,
2787         "no-arp+" => \$no_arp,
2788            );
2790 #  read and set config parameters
2791 &check_cmdline_param ;
2792 &read_configfile;
2793 &check_pid;
2795 $SIG{CHLD} = 'IGNORE';
2797 # forward error messages to logfile
2798 if( ! $foreground ) {
2799   open( STDIN,  '+>/dev/null' );
2800   open( STDOUT, '+>&STDIN'    );
2801   open( STDERR, '+>&STDIN'    );
2804 # Just fork, if we are not in foreground mode
2805 if( ! $foreground ) { 
2806     chdir '/'                 or die "Can't chdir to /: $!";
2807     $pid = fork;
2808     setsid                    or die "Can't start a new session: $!";
2809     umask 0;
2810 } else { 
2811     $pid = $$; 
2814 # Do something useful - put our PID into the pid_file
2815 if( 0 != $pid ) {
2816     open( LOCK_FILE, ">$pid_file" );
2817     print LOCK_FILE "$pid\n";
2818     close( LOCK_FILE );
2819     if( !$foreground ) { 
2820         exit( 0 ) 
2821     };
2824 # parse head url and revision from svn
2825 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2826 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2827 $server_headURL = defined $1 ? $1 : 'unknown' ;
2828 $server_revision = defined $2 ? $2 : 'unknown' ;
2829 if ($server_headURL =~ /\/tag\// || 
2830         $server_headURL =~ /\/branches\// ) {
2831     $server_status = "stable"; 
2832 } else {
2833     $server_status = "developmental" ;
2837 daemon_log(" ", 1);
2838 daemon_log("$0 started!", 1);
2839 daemon_log("status: $server_status", 1);
2840 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2842 # connect to incoming_db
2843 unlink($incoming_file_name);
2844 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2845 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2847 # connect to gosa-si job queue
2848 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2849 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2851 # connect to known_clients_db
2852 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2853 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2855 # connect to foreign_clients_db
2856 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2857 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2859 # connect to known_server_db
2860 unlink($known_server_file_name);
2861 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2862 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2864 # connect to login_usr_db
2865 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2866 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2868 # connect to fai_server_db and fai_release_db
2869 unlink($fai_server_file_name);
2870 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2871 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2873 unlink($fai_release_file_name);
2874 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2875 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2877 # connect to packages_list_db
2878 #unlink($packages_list_file_name);
2879 unlink($packages_list_under_construction);
2880 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2881 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2883 # connect to messaging_db
2884 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2885 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2888 # create xml object used for en/decrypting
2889 $xml = new XML::Simple();
2892 # foreign servers 
2893 my @foreign_server_list;
2895 # add foreign server from cfg file
2896 if ($foreign_server_string ne "") {
2897     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2898     foreach my $foreign_server (@cfg_foreign_server_list) {
2899         push(@foreign_server_list, $foreign_server);
2900     }
2903 # add foreign server from dns
2904 my @tmp_servers;
2905 if ( !$server_domain) {
2906     # Try our DNS Searchlist
2907     for my $domain(get_dns_domains()) {
2908         chomp($domain);
2909         my @tmp_domains= &get_server_addresses($domain);
2910         if(@tmp_domains) {
2911             for my $tmp_server(@tmp_domains) {
2912                 push @tmp_servers, $tmp_server;
2913             }
2914         }
2915     }
2916     if(@tmp_servers && length(@tmp_servers)==0) {
2917         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2918     }
2919 } else {
2920     @tmp_servers = &get_server_addresses($server_domain);
2921     if( 0 == @tmp_servers ) {
2922         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2923     }
2925 foreach my $server (@tmp_servers) { 
2926     unshift(@foreign_server_list, $server); 
2928 # eliminate duplicate entries
2929 @foreign_server_list = &del_doubles(@foreign_server_list);
2930 my $all_foreign_server = join(", ", @foreign_server_list);
2931 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2933 # add all found foreign servers to known_server
2934 my $act_timestamp = &get_time();
2935 foreach my $foreign_server (@foreign_server_list) {
2936     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2937             primkey=>['hostname'],
2938             hostname=>$foreign_server,
2939             status=>'not_jet_registered',
2940             hostkey=>"none",
2941             timestamp=>$act_timestamp,
2942             } );
2946 POE::Component::Server::TCP->new(
2947     Alias => "TCP_SERVER",
2948         Port => $server_port,
2949         ClientInput => sub {
2950         my ($kernel, $input) = @_[KERNEL, ARG0];
2951         push(@tasks, $input);
2952         push(@msgs_to_decrypt, $input);
2953         $kernel->yield("msg_to_decrypt");
2954         },
2955     InlineStates => {
2956         msg_to_decrypt => \&msg_to_decrypt,
2957         next_task => \&next_task,
2958         task_result => \&handle_task_result,
2959         task_done   => \&handle_task_done,
2960         task_debug  => \&handle_task_debug,
2961         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2962     }
2963 );
2965 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2967 # create session for repeatedly checking the job queue for jobs
2968 POE::Session->create(
2969         inline_states => {
2970                 _start => \&session_start,
2971         register_at_foreign_servers => \&register_at_foreign_servers,
2972         sig_handler => \&sig_handler,
2973         next_task => \&next_task,
2974         task_result => \&handle_task_result,
2975         task_done   => \&handle_task_done,
2976         task_debug  => \&handle_task_debug,
2977         watch_for_next_tasks => \&watch_for_next_tasks,
2978         watch_for_new_messages => \&watch_for_new_messages,
2979         watch_for_delivery_messages => \&watch_for_delivery_messages,
2980         watch_for_done_messages => \&watch_for_done_messages,
2981                 watch_for_new_jobs => \&watch_for_new_jobs,
2982         watch_for_done_jobs => \&watch_for_done_jobs,
2983         watch_for_old_known_clients => \&watch_for_old_known_clients,
2984         create_packages_list_db => \&run_create_packages_list_db,
2985         create_fai_server_db => \&run_create_fai_server_db,
2986         create_fai_release_db => \&run_create_fai_release_db,
2987         session_run_result => \&session_run_result,
2988         session_run_debug => \&session_run_debug,
2989         session_run_done => \&session_run_done,
2990         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2991         }
2992 );
2995 # import all modules
2996 &import_modules;
2998 # TODO
2999 # check wether all modules are gosa-si valid passwd check
3003 POE::Kernel->run();
3004 exit;