Code

f2f26ada805ec0df82fe9684ed65a776e1a25d62
[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);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
82 # Variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_timeout,
87     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89     $arp_enabled, $arp_interface,
90     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91                 $new_systems_ou,
92 );
94 # additional variable which should be globaly accessable
95 our $server_address;
96 our $server_mac_address;
97 our $gosa_address;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 our $known_modules;
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 # holds all incoming decrypted messages
120 our $incoming_db;
121 our $incoming_tn = 'incoming';
122 my $incoming_file_name;
123 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
124         "timestamp DEFAULT 'none'", 
125         "headertag DEFAULT 'none'",
126                 "targettag DEFAULT 'none'",
127         "xmlmessage DEFAULT 'none'",
128         "module DEFAULT 'none'",
129         "sessionid DEFAULT '0'",
130         );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
137                 "timestamp DEFAULT 'none'", 
138                 "status DEFAULT 'none'", 
139                 "result DEFAULT 'none'", 
140                 "progress DEFAULT 'none'", 
141         "headertag DEFAULT 'none'", 
142                 "targettag DEFAULT 'none'", 
143                 "xmlmessage DEFAULT 'none'", 
144                 "macaddress DEFAULT 'none'",
145                 "plainname DEFAULT 'none'",
146         "siserver DEFAULT 'none'",
147         "modified DEFAULT '0'",
148                 );
150 # holds all other gosa-si-server
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients"; 
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client 
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server"; 
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release"; 
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386"; 
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging"; 
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
197         "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai"; 
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 # loop delay for job queue to look for opsi jobs
210 my $job_queue_opsi_delay = 10;
211 our $opsi_client;
212 our $opsi_url;
213  
214 # Lifetime of logged in user information. If no update information comes after n seconds, 
215 # the user is expeceted to be no longer logged in or the host is no longer running. Because
216 # of this, the user is deleted from login_users_db
217 our $logged_in_user_date_of_expiry = 600;
220 %cfg_defaults = (
221 "general" => {
222     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
223     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
224     },
225 "server" => {
226     "ip"                    => [\$server_ip, "0.0.0.0"],
227     "port"                  => [\$server_port, "20081"],
228     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
229     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
230     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
231     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
232     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
233     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
234     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
235     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
236     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
237     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
238     "repo-path"             => [\$repo_path, '/srv/www/repository'],
239     "ldap-uri"              => [\$ldap_uri, ""],
240     "ldap-base"             => [\$ldap_base, ""],
241     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
242     "ldap-admin-password"   => [\$ldap_admin_password, ""],
243     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
244     "max-clients"           => [\$max_clients, 10],
245     "wol-password"          => [\$wake_on_lan_passwd, ""],
246     },
247 "GOsaPackages" => {
248     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
249     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
250     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
251     "key" => [\$GosaPackages_key, "none"],
252                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
253     },
254 "ClientPackages" => {
255     "key" => [\$ClientPackages_key, "none"],
256     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
257     },
258 "ServerPackages"=> {
259     "address"      => [\$foreign_server_string, ""],
260     "dns-lookup"            => [\$dns_lookup, "true"],
261     "domain"  => [\$server_domain, ""],
262     "key"     => [\$ServerPackages_key, "none"],
263     "key-lifetime" => [\$foreign_servers_register_delay, 120],
264     "job-synchronization-enabled" => [\$job_synchronization, "true"],
265     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
266     },
267 "ArpHandler" => {
268     "enabled"   => [\$arp_enabled, "true"],
269     "interface" => [\$arp_interface, "all"],
270         },
271 "Opsi" => {
272     "enabled"  => [\$opsi_enabled, "false"], 
273     "server"   => [\$opsi_server, "localhost"],
274     "admin"    => [\$opsi_admin, "opsi-admin"],
275     "password" => [\$opsi_password, "secret"],
276    },
278 );
281 #===  FUNCTION  ================================================================
282 #         NAME:  usage
283 #   PARAMETERS:  nothing
284 #      RETURNS:  nothing
285 #  DESCRIPTION:  print out usage text to STDERR
286 #===============================================================================
287 sub usage {
288     print STDERR << "EOF" ;
289 usage: $prg [-hvf] [-c config]
291            -h        : this (help) message
292            -c <file> : config file
293            -f        : foreground, process will not be forked to background
294            -v        : be verbose (multiple to increase verbosity)
295            -no-arp   : starts $prg without connection to arp module
296  
297 EOF
298     print "\n" ;
302 #===  FUNCTION  ================================================================
303 #         NAME:  logging
304 #   PARAMETERS:  level - string - default 'info'
305 #                msg - string -
306 #                facility - string - default 'LOG_DAEMON'
307 #      RETURNS:  nothing
308 #  DESCRIPTION:  function for logging
309 #===============================================================================
310 sub daemon_log {
311     # log into log_file
312     my( $msg, $level ) = @_;
313     if(not defined $msg) { return }
314     if(not defined $level) { $level = 1 }
315     if(defined $log_file){
316         open(LOG_HANDLE, ">>$log_file");
317         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
318             print STDERR "cannot open $log_file: $!";
319             return 
320         }
321         chomp($msg);
322         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
323         if($level <= $verbose){
324             my ($seconds, $minutes, $hours, $monthday, $month,
325                     $year, $weekday, $yearday, $sommertime) = localtime(time);
326             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
327             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
328             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
329             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
330             $month = $monthnames[$month];
331             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
332             $year+=1900;
333             my $name = $prg;
335             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
336             print LOG_HANDLE $log_msg;
337             if( $foreground ) { 
338                 print STDERR $log_msg;
339             }
340         }
341         close( LOG_HANDLE );
342     }
346 #===  FUNCTION  ================================================================
347 #         NAME:  check_cmdline_param
348 #   PARAMETERS:  nothing
349 #      RETURNS:  nothing
350 #  DESCRIPTION:  validates commandline parameter
351 #===============================================================================
352 sub check_cmdline_param () {
353     my $err_config;
354     my $err_counter = 0;
355         if(not defined($cfg_file)) {
356                 $cfg_file = "/etc/gosa-si/server.conf";
357                 if(! -r $cfg_file) {
358                         $err_config = "please specify a config file";
359                         $err_counter += 1;
360                 }
361     }
362     if( $err_counter > 0 ) {
363         &usage( "", 1 );
364         if( defined( $err_config)) { print STDERR "$err_config\n"}
365         print STDERR "\n";
366         exit( -1 );
367     }
371 #===  FUNCTION  ================================================================
372 #         NAME:  check_pid
373 #   PARAMETERS:  nothing
374 #      RETURNS:  nothing
375 #  DESCRIPTION:  handels pid processing
376 #===============================================================================
377 sub check_pid {
378     $pid = -1;
379     # Check, if we are already running
380     if( open(LOCK_FILE, "<$pid_file") ) {
381         $pid = <LOCK_FILE>;
382         if( defined $pid ) {
383             chomp( $pid );
384             if( -f "/proc/$pid/stat" ) {
385                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
386                 if( $stat ) {
387                                         daemon_log("ERROR: Already running",1);
388                     close( LOCK_FILE );
389                     exit -1;
390                 }
391             }
392         }
393         close( LOCK_FILE );
394         unlink( $pid_file );
395     }
397     # create a syslog msg if it is not to possible to open PID file
398     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
399         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
400         if (open(LOCK_FILE, '<', $pid_file)
401                 && ($pid = <LOCK_FILE>))
402         {
403             chomp($pid);
404             $msg .= "(PID $pid)\n";
405         } else {
406             $msg .= "(unable to read PID)\n";
407         }
408         if( ! ($foreground) ) {
409             openlog( $0, "cons,pid", "daemon" );
410             syslog( "warning", $msg );
411             closelog();
412         }
413         else {
414             print( STDERR " $msg " );
415         }
416         exit( -1 );
417     }
420 #===  FUNCTION  ================================================================
421 #         NAME:  import_modules
422 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
423 #                are stored
424 #      RETURNS:  nothing
425 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
426 #                state is on is imported by "require 'file';"
427 #===============================================================================
428 sub import_modules {
429     daemon_log(" ", 1);
431     if (not -e $modules_path) {
432         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
433     }
435     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
436     while (defined (my $file = readdir (DIR))) {
437         if (not $file =~ /(\S*?).pm$/) {
438             next;
439         }
440                 my $mod_name = $1;
442         # ArpHandler switch
443         if( $file =~ /ArpHandler.pm/ ) {
444             if( $arp_enabled eq "false" ) { next; }
445         }
446         
447         eval { require $file; };
448         if ($@) {
449             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
450             daemon_log("$@", 5);
451                 } else {
452                         my $info = eval($mod_name.'::get_module_info()');
453                         # Only load module if get_module_info() returns a non-null object
454                         if( $info ) {
455                                 my ($input_address, $input_key, $event_hash) = @{$info};
456                                 $known_modules->{$mod_name} = $info;
457                                 daemon_log("0 INFO: module $mod_name loaded", 5);
458                         }
459                 }
460     }   
462     close (DIR);
465 #===  FUNCTION  ================================================================
466 #         NAME:  password_check
467 #   PARAMETERS:  nothing
468 #      RETURNS:  nothing
469 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
470 #                the same password
471 #===============================================================================
472 sub password_check {
473     my $passwd_hash = {};
474     while (my ($mod_name, $mod_info) = each %$known_modules) {
475         my $mod_passwd = @$mod_info[1];
476         if (not defined $mod_passwd) { next; }
477         if (not exists $passwd_hash->{$mod_passwd}) {
478             $passwd_hash->{$mod_passwd} = $mod_name;
480         # escalates critical error
481         } else {
482             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
483             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
484             exit( -1 );
485         }
486     }
491 #===  FUNCTION  ================================================================
492 #         NAME:  sig_int_handler
493 #   PARAMETERS:  signal - string - signal arose from system
494 #      RETURNS:  nothing
495 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
496 #===============================================================================
497 sub sig_int_handler {
498     my ($signal) = @_;
500 #       if (defined($ldap_handle)) {
501 #               $ldap_handle->disconnect;
502 #       }
503     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
504     
506     daemon_log("shutting down gosa-si-server", 1);
507     system("kill `ps -C gosa-si-server -o pid=`");
509 $SIG{INT} = \&sig_int_handler;
512 sub check_key_and_xml_validity {
513     my ($crypted_msg, $module_key, $session_id) = @_;
514     my $msg;
515     my $msg_hash;
516     my $error_string;
517     eval{
518         $msg = &decrypt_msg($crypted_msg, $module_key);
520         if ($msg =~ /<xml>/i){
521             $msg =~ s/\s+/ /g;  # just for better daemon_log
522             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
523             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
525             ##############
526             # check header
527             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
528             my $header_l = $msg_hash->{'header'};
529             if( 1 > @{$header_l} ) { die 'empty header tag'; }
530             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
531             my $header = @{$header_l}[0];
532             if( 0 == length $header) { die 'empty string in header tag'; }
534             ##############
535             # check source
536             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
537             my $source_l = $msg_hash->{'source'};
538             if( 1 > @{$source_l} ) { die 'empty source tag'; }
539             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
540             my $source = @{$source_l}[0];
541             if( 0 == length $source) { die 'source error'; }
543             ##############
544             # check target
545             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
546             my $target_l = $msg_hash->{'target'};
547             if( 1 > @{$target_l} ) { die 'empty target tag'; }
548         }
549     };
550     if($@) {
551         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
552         $msg = undef;
553         $msg_hash = undef;
554     }
556     return ($msg, $msg_hash);
560 sub check_outgoing_xml_validity {
561     my ($msg, $session_id) = @_;
563     my $msg_hash;
564     eval{
565         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
567         ##############
568         # check header
569         my $header_l = $msg_hash->{'header'};
570         if( 1 != @{$header_l} ) {
571             die 'no or more than one headers specified';
572         }
573         my $header = @{$header_l}[0];
574         if( 0 == length $header) {
575             die 'header has length 0';
576         }
578         ##############
579         # check source
580         my $source_l = $msg_hash->{'source'};
581         if( 1 != @{$source_l} ) {
582             die 'no or more than 1 sources specified';
583         }
584         my $source = @{$source_l}[0];
585         if( 0 == length $source) {
586             die 'source has length 0';
587         }
588         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
589                 $source =~ /^GOSA$/i ) {
590             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
591         }
592         
593         ##############
594         # check target  
595         my $target_l = $msg_hash->{'target'};
596         if( 0 == @{$target_l} ) {
597             die "no targets specified";
598         }
599         foreach my $target (@$target_l) {
600             if( 0 == length $target) {
601                 die "target has length 0";
602             }
603             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
604                     $target =~ /^GOSA$/i ||
605                     $target =~ /^\*$/ ||
606                     $target =~ /KNOWN_SERVER/i ||
607                     $target =~ /JOBDB/i ||
608                     $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 ){
609                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
610             }
611         }
612     };
613     if($@) {
614         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
615         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
616         $msg_hash = undef;
617     }
619     return ($msg_hash);
623 sub input_from_known_server {
624     my ($input, $remote_ip, $session_id) = @_ ;  
625     my ($msg, $msg_hash, $module);
627     my $sql_statement= "SELECT * FROM known_server";
628     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
630     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
631         my $host_name = $hit->{hostname};
632         if( not $host_name =~ "^$remote_ip") {
633             next;
634         }
635         my $host_key = $hit->{hostkey};
636         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
637         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
639         # check if module can open msg envelope with module key
640         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
641         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
642             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
643             daemon_log("$@", 8);
644             next;
645         }
646         else {
647             $msg = $tmp_msg;
648             $msg_hash = $tmp_msg_hash;
649             $module = "ServerPackages";
650             last;
651         }
652     }
654     if( (!$msg) || (!$msg_hash) || (!$module) ) {
655         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
656     }
657   
658     return ($msg, $msg_hash, $module);
662 sub input_from_known_client {
663     my ($input, $remote_ip, $session_id) = @_ ;  
664     my ($msg, $msg_hash, $module);
666     my $sql_statement= "SELECT * FROM known_clients";
667     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
668     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
669         my $host_name = $hit->{hostname};
670         if( not $host_name =~ /^$remote_ip:\d*$/) {
671                 next;
672                 }
673         my $host_key = $hit->{hostkey};
674         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
675         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
677         # check if module can open msg envelope with module key
678         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
680         if( (!$msg) || (!$msg_hash) ) {
681             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
682             &daemon_log("$@", 8);
683             next;
684         }
685         else {
686             $module = "ClientPackages";
687             last;
688         }
689     }
691     if( (!$msg) || (!$msg_hash) || (!$module) ) {
692         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
693     }
695     return ($msg, $msg_hash, $module);
699 sub input_from_unknown_host {
700         no strict "refs";
701         my ($input, $session_id) = @_ ;
702         my ($msg, $msg_hash, $module);
703         my $error_string;
705         my %act_modules = %$known_modules;
707         while( my ($mod, $info) = each(%act_modules)) {
709                 # check a key exists for this module
710                 my $module_key = ${$mod."_key"};
711                 if( not defined $module_key ) {
712                         if( $mod eq 'ArpHandler' ) {
713                                 next;
714                         }
715                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
716                         next;
717                 }
718                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
720                 # check if module can open msg envelope with module key
721                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
722                 if( (not defined $msg) || (not defined $msg_hash) ) {
723                         next;
724                 } else {
725                         $module = $mod;
726                         last;
727                 }
728         }
730         if( (!$msg) || (!$msg_hash) || (!$module)) {
731                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
732         }
734         return ($msg, $msg_hash, $module);
738 sub create_ciphering {
739     my ($passwd) = @_;
740         if((!defined($passwd)) || length($passwd)==0) {
741                 $passwd = "";
742         }
743     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
744     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
745     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
746     $my_cipher->set_iv($iv);
747     return $my_cipher;
751 sub encrypt_msg {
752     my ($msg, $key) = @_;
753     my $my_cipher = &create_ciphering($key);
754     my $len;
755     {
756             use bytes;
757             $len= 16-length($msg)%16;
758     }
759     $msg = "\0"x($len).$msg;
760     $msg = $my_cipher->encrypt($msg);
761     chomp($msg = &encode_base64($msg));
762     # there are no newlines allowed inside msg
763     $msg=~ s/\n//g;
764     return $msg;
768 sub decrypt_msg {
770     my ($msg, $key) = @_ ;
771     $msg = &decode_base64($msg);
772     my $my_cipher = &create_ciphering($key);
773     $msg = $my_cipher->decrypt($msg); 
774     $msg =~ s/\0*//g;
775     return $msg;
779 sub get_encrypt_key {
780     my ($target) = @_ ;
781     my $encrypt_key;
782     my $error = 0;
784     # target can be in known_server
785     if( not defined $encrypt_key ) {
786         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
787         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
788         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
789             my $host_name = $hit->{hostname};
790             if( $host_name ne $target ) {
791                 next;
792             }
793             $encrypt_key = $hit->{hostkey};
794             last;
795         }
796     }
798     # target can be in known_client
799     if( not defined $encrypt_key ) {
800         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
801         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
802         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
803             my $host_name = $hit->{hostname};
804             if( $host_name ne $target ) {
805                 next;
806             }
807             $encrypt_key = $hit->{hostkey};
808             last;
809         }
810     }
812     return $encrypt_key;
816 #===  FUNCTION  ================================================================
817 #         NAME:  open_socket
818 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
819 #                [PeerPort] string necessary if port not appended by PeerAddr
820 #      RETURNS:  socket IO::Socket::INET
821 #  DESCRIPTION:  open a socket to PeerAddr
822 #===============================================================================
823 sub open_socket {
824     my ($PeerAddr, $PeerPort) = @_ ;
825     if(defined($PeerPort)){
826         $PeerAddr = $PeerAddr.":".$PeerPort;
827     }
828     my $socket;
829     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
830             Porto => "tcp",
831             Type => SOCK_STREAM,
832             Timeout => 5,
833             );
834     if(not defined $socket) {
835         return;
836     }
837 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
838     return $socket;
842 #sub get_local_ip_for_remote_ip {
843 #       my $remote_ip= shift;
844 #       my $result="0.0.0.0";
846 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
847 #               if($remote_ip eq "127.0.0.1") {
848 #                       $result = "127.0.0.1";
849 #               } else {
850 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
852 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
853 #                               or die "Could not open $PROC_NET_ROUTE";
855 #                       my @ifs = <PROC_NET_ROUTE>;
857 #                       close(PROC_NET_ROUTE);
859 #                       # Eat header line
860 #                       shift @ifs;
861 #                       chomp @ifs;
862 #                       foreach my $line(@ifs) {
863 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
864 #                               my $destination;
865 #                               my $mask;
866 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
867 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
868 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
869 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
870 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
871 #                                       # destination matches route, save mac and exit
872 #                                       $result= &get_ip($Iface);
873 #                                       last;
874 #                               }
875 #                       }
876 #               }
877 #       } else {
878 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
879 #       }
880 #       return $result;
881 #}
884 sub send_msg_to_target {
885     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
886     my $error = 0;
887     my $header;
888     my $timestamp = &get_time();
889     my $new_status;
890     my $act_status;
891     my ($sql_statement, $res);
892   
893     if( $msg_header ) {
894         $header = "'$msg_header'-";
895     } else {
896         $header = "";
897     }
899         # Patch the source ip
900         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
901                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
902                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
903         }
905     # encrypt xml msg
906     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
908     # opensocket
909     my $socket = &open_socket($address);
910     if( !$socket ) {
911         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
912         $error++;
913     }
914     
915     if( $error == 0 ) {
916         # send xml msg
917         print $socket $crypted_msg."\n";
919         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
920         daemon_log("$session_id DEBUG: message:\n$msg", 9);
921         
922     }
924     # close socket in any case
925     if( $socket ) {
926         close $socket;
927     }
929     if( $error > 0 ) { $new_status = "down"; }
930     else { $new_status = $msg_header; }
933     # known_clients
934     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
935     $res = $known_clients_db->select_dbentry($sql_statement);
936     if( keys(%$res) == 1) {
937         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
938         if ($act_status eq "down" && $new_status eq "down") {
939             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
940             $res = $known_clients_db->del_dbentry($sql_statement);
941             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
942         } else { 
943             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
944             $res = $known_clients_db->update_dbentry($sql_statement);
945             if($new_status eq "down"){
946                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
947             } else {
948                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
949             }
950         }
951     }
953     # known_server
954     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
955     $res = $known_server_db->select_dbentry($sql_statement);
956     if( keys(%$res) == 1) {
957         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
958         if ($act_status eq "down" && $new_status eq "down") {
959             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
960             $res = $known_server_db->del_dbentry($sql_statement);
961             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
962         } 
963         else { 
964             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
965             $res = $known_server_db->update_dbentry($sql_statement);
966             if($new_status eq "down"){
967                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
968             } else {
969                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
970             }
971         }
972     }
973     return $error; 
977 sub update_jobdb_status_for_send_msgs {
978     my ($answer, $error) = @_;
979     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
980         my $jobdb_id = $1;
981             
982         # sending msg faild
983         if( $error ) {
984             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
985                 my $sql_statement = "UPDATE $job_queue_tn ".
986                     "SET status='error', result='can not deliver msg, please consult log file' ".
987                     "WHERE id=$jobdb_id";
988                 my $res = $job_db->update_dbentry($sql_statement);
989             }
991         # sending msg was successful
992         } else {
993             my $sql_statement = "UPDATE $job_queue_tn ".
994                 "SET status='done' ".
995                 "WHERE id=$jobdb_id AND status='processed'";
996             my $res = $job_db->update_dbentry($sql_statement);
997         }
998     }
1002 sub sig_handler {
1003         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1004         daemon_log("0 INFO got signal '$signal'", 1); 
1005         $kernel->sig_handled();
1006         return;
1010 sub msg_to_decrypt {
1011     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1012     my $session_id = $session->ID;
1013     my ($msg, $msg_hash, $module);
1014     my $error = 0;
1016     # hole neue msg aus @msgs_to_decrypt
1017     my $next_msg = shift @msgs_to_decrypt;
1018     
1019     # entschlüssle sie
1021     # msg is from a new client or gosa
1022     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1023     # msg is from a gosa-si-server
1024     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1025         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1026     }
1027     # msg is from a gosa-si-client
1028     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1029         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1030     }
1031     # an error occurred
1032     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1034         # could not understand a msg from its server the client cause a re-registering process
1035         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1036                 "' to cause a re-registering of the client if necessary", 3);
1037         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1038         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1039         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1040             my $host_name = $hit->{'hostname'};
1041             my $host_key = $hit->{'hostkey'};
1042             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1043             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1044             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1045         }
1046         $error++;
1047     }
1050     my $header;
1051     my $target;
1052     my $source;
1053     my $done = 0;
1054     my $sql;
1055     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 $not_found_in_known_clients_db = 0;
1063                 my $not_found_in_known_server_db = 0;
1064                 my $not_found_in_foreign_clients_db = 0;
1065         my $local_address;
1066         my $local_mac;
1067         my ($target_ip, $target_port) = split(':', $target);
1068         
1069         # Determine the local ip address if target is an ip address
1070                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1071                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1072                 } else {
1073             $local_address = $server_address;
1074         }
1076         # Determine the local mac address if target is a mac address
1077         if ($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) {
1078             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1079             my $network_interface= &get_interface_for_ip($loc_ip);
1080             $local_mac = &get_mac_for_interface($network_interface);
1081         } else {
1082             $local_mac = $server_mac_address;
1083         }
1085         # target and source is equal to GOSA -> process here
1086         if (not $done) {
1087             if ($target eq "GOSA" && $source eq "GOSA") {
1088                 $done = 1;                    
1089                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1090             }
1091         }
1093         # target is own address without forward_to_gosa-tag -> process here
1094         if (not $done) {
1095             #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1096             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1097                 $done = 1;
1098                 if ($source eq "GOSA") {
1099                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1100                 }
1101                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1102             }
1103         }
1105         # target is a client address in known_clients -> process here
1106                 if (not $done) {
1107                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1108                                 $res = $known_clients_db->select_dbentry($sql);
1109                                 if (keys(%$res) > 0) {
1110                                                 $done = 1; 
1111                                                 my $hostname = $res->{1}->{'hostname'};
1112                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1113                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1114                         if ($source eq "GOSA") {
1115                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1116                         }
1117                         &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1119                                 } else {
1120                                                 $not_found_in_known_clients_db = 1;
1121                                 }
1122                 }
1123         
1124         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1125         if (not $done) {
1126             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1127             my $gosa_at;
1128             my $gosa_session_id;
1129             if (($target eq $local_address) && (defined $forward_to_gosa)){
1130                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1131                 if ($gosa_at ne $local_address) {
1132                     $done = 1;
1133                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1134                 }
1135             }
1136         }
1138         # if message should be processed here -> add message to incoming_db
1139                 if ($done) {
1140                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1141                                 # so gosa-si-server knows how to process this kind of messages
1142                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1143                                                 $module = "GosaPackages";
1144                                 }
1146                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1147                                                                 primkey=>[],
1148                                                                 headertag=>$header,
1149                                                                 targettag=>$target,
1150                                                                 xmlmessage=>&encode_base64($msg),
1151                                                                 timestamp=>&get_time,
1152                                                                 module=>$module,
1153                                                                 sessionid=>$session_id,
1154                                                                 } );
1155                 }
1157         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1158         if (not $done) {
1159             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1160             my $gosa_at;
1161             my $gosa_session_id;
1162             if (($target eq $local_address) && (defined $forward_to_gosa)){
1163                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1164                 if ($gosa_at eq $local_address) {
1165                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1166                     if( defined $session_reference ) {
1167                         $heap = $session_reference->get_heap();
1168                     }
1169                     if(exists $heap->{'client'}) {
1170                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1171                         $heap->{'client'}->put($msg);
1172                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1173                     }
1174                     $done = 1;
1175                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1176                 }
1177             }
1179         }
1181         # target is a client address in foreign_clients -> forward to registration server
1182         if (not $done) {
1183             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1184             $res = $foreign_clients_db->select_dbentry($sql);
1185             if (keys(%$res) > 0) {
1186                     my $hostname = $res->{1}->{'hostname'};
1187                     my ($host_ip, $host_port) = split(/:/, $hostname);
1188                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1189                 my $regserver = $res->{1}->{'regserver'};
1190                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1191                 my $res = $known_server_db->select_dbentry($sql);
1192                 if (keys(%$res) > 0) {
1193                     my $regserver_key = $res->{1}->{'hostkey'};
1194                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1195                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1196                     if ($source eq "GOSA") {
1197                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1198                     }
1199                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1200                 }
1201                 $done = 1;
1202                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1203             } else {
1204                                 $not_found_in_foreign_clients_db = 1;
1205                         }
1206         }
1208         # target is a server address -> forward to server
1209         if (not $done) {
1210             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1211             $res = $known_server_db->select_dbentry($sql);
1212             if (keys(%$res) > 0) {
1213                 my $hostkey = $res->{1}->{'hostkey'};
1215                 if ($source eq "GOSA") {
1216                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1217                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1219                 }
1221                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1222                 $done = 1;
1223                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1224             } else {
1225                                 $not_found_in_known_server_db = 1;
1226                         }
1227         }
1229                 
1230                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1231                 if ( $not_found_in_foreign_clients_db 
1232                                                 && $not_found_in_known_server_db
1233                                                 && $not_found_in_known_clients_db) {
1234                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1235                                                                 primkey=>[],
1236                                                                 headertag=>$header,
1237                                                                 targettag=>$target,
1238                                                                 xmlmessage=>&encode_base64($msg),
1239                                                                 timestamp=>&get_time,
1240                                                                 module=>$module,
1241                                                                 sessionid=>$session_id,
1242                                                                 } );
1243                                 $done = 1;
1244                 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1245                 }
1248         if (not $done) {
1249             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1250             if ($source eq "GOSA") {
1251                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1252                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1254                 my $session_reference = $kernel->ID_id_to_session($session_id);
1255                 if( defined $session_reference ) {
1256                     $heap = $session_reference->get_heap();
1257                 }
1258                 if(exists $heap->{'client'}) {
1259                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1260                     $heap->{'client'}->put($error_msg);
1261                 }
1262             }
1263         }
1265     }
1267     return;
1271 sub next_task {
1272     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1273     my $running_task = POE::Wheel::Run->new(
1274             Program => sub { process_task($session, $heap, $task) },
1275             StdioFilter => POE::Filter::Reference->new(),
1276             StdoutEvent  => "task_result",
1277             StderrEvent  => "task_debug",
1278             CloseEvent   => "task_done",
1279             );
1280     $heap->{task}->{ $running_task->ID } = $running_task;
1283 sub handle_task_result {
1284     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1285     my $client_answer = $result->{'answer'};
1286     if( $client_answer =~ s/session_id=(\d+)$// ) {
1287         my $session_id = $1;
1288         if( defined $session_id ) {
1289             my $session_reference = $kernel->ID_id_to_session($session_id);
1290             if( defined $session_reference ) {
1291                 $heap = $session_reference->get_heap();
1292             }
1293         }
1295         if(exists $heap->{'client'}) {
1296             $heap->{'client'}->put($client_answer);
1297         }
1298     }
1299     $kernel->sig(CHLD => "child_reap");
1302 sub handle_task_debug {
1303     my $result = $_[ARG0];
1304     print STDERR "$result\n";
1307 sub handle_task_done {
1308     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1309     delete $heap->{task}->{$task_id};
1312 sub process_task {
1313     no strict "refs";
1314     #CHECK: Not @_[...]?
1315     my ($session, $heap, $task) = @_;
1316     my $error = 0;
1317     my $answer_l;
1318     my ($answer_header, @answer_target_l, $answer_source);
1319     my $client_answer = "";
1321     # prepare all variables needed to process message
1322     #my $msg = $task->{'xmlmessage'};
1323     my $msg = &decode_base64($task->{'xmlmessage'});
1324     my $incoming_id = $task->{'id'};
1325     my $module = $task->{'module'};
1326     my $header =  $task->{'headertag'};
1327     my $session_id = $task->{'sessionid'};
1328     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1329     my $source = @{$msg_hash->{'source'}}[0];
1330     
1331     # set timestamp of incoming client uptodate, so client will not 
1332     # be deleted from known_clients because of expiration
1333     my $act_time = &get_time();
1334     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1335     my $res = $known_clients_db->exec_statement($sql);
1337     ######################
1338     # process incoming msg
1339     if( $error == 0) {
1340         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1341         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1342         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1344         if ( 0 < @{$answer_l} ) {
1345             my $answer_str = join("\n", @{$answer_l});
1346             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1347                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1348             }
1349             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1350         } else {
1351             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1352         }
1354     }
1355     if( !$answer_l ) { $error++ };
1357     ########
1358     # answer
1359     if( $error == 0 ) {
1361         foreach my $answer ( @{$answer_l} ) {
1362             # check outgoing msg to xml validity
1363             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1364             if( not defined $answer_hash ) { next; }
1365             
1366             $answer_header = @{$answer_hash->{'header'}}[0];
1367             @answer_target_l = @{$answer_hash->{'target'}};
1368             $answer_source = @{$answer_hash->{'source'}}[0];
1370             # deliver msg to all targets 
1371             foreach my $answer_target ( @answer_target_l ) {
1373                 # targets of msg are all gosa-si-clients in known_clients_db
1374                 if( $answer_target eq "*" ) {
1375                     # answer is for all clients
1376                     my $sql_statement= "SELECT * FROM known_clients";
1377                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1378                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1379                         my $host_name = $hit->{hostname};
1380                         my $host_key = $hit->{hostkey};
1381                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1382                         &update_jobdb_status_for_send_msgs($answer, $error);
1383                     }
1384                 }
1386                 # targets of msg are all gosa-si-server in known_server_db
1387                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1388                     # answer is for all server in known_server
1389                     my $sql_statement= "SELECT * FROM $known_server_tn";
1390                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1391                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1392                         my $host_name = $hit->{hostname};
1393                         my $host_key = $hit->{hostkey};
1394                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1395                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1396                         &update_jobdb_status_for_send_msgs($answer, $error);
1397                     }
1398                 }
1400                 # target of msg is GOsa
1401                                 elsif( $answer_target eq "GOSA" ) {
1402                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1403                                         my $add_on = "";
1404                     if( defined $session_id ) {
1405                         $add_on = ".session_id=$session_id";
1406                     }
1407                     # answer is for GOSA and has to returned to connected client
1408                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1409                     $client_answer = $gosa_answer.$add_on;
1410                 }
1412                 # target of msg is job queue at this host
1413                 elsif( $answer_target eq "JOBDB") {
1414                     $answer =~ /<header>(\S+)<\/header>/;   
1415                     my $header;
1416                     if( defined $1 ) { $header = $1; }
1417                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1418                     &update_jobdb_status_for_send_msgs($answer, $error);
1419                 }
1421                 # Target of msg is a mac address
1422                 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 ) {
1423                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1424                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1425                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1426                     my $found_ip_flag = 0;
1427                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1428                         my $host_name = $hit->{hostname};
1429                         my $host_key = $hit->{hostkey};
1430                         $answer =~ s/$answer_target/$host_name/g;
1431                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1432                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1433                         &update_jobdb_status_for_send_msgs($answer, $error);
1434                         $found_ip_flag++ ;
1435                     }   
1436                     if ($found_ip_flag == 0) {
1437                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1438                         my $res = $foreign_clients_db->select_dbentry($sql);
1439                         while( my ($hit_num, $hit) = each %{ $res } ) {
1440                             my $host_name = $hit->{hostname};
1441                             my $reg_server = $hit->{regserver};
1442                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1443                             
1444                             # Fetch key for reg_server
1445                             my $reg_server_key;
1446                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1447                             my $res = $known_server_db->select_dbentry($sql);
1448                             if (exists $res->{1}) {
1449                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1450                             } else {
1451                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1452                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1453                                 $reg_server_key = undef;
1454                             }
1456                             # Send answer to server where client is registered
1457                             if (defined $reg_server_key) {
1458                                 $answer =~ s/$answer_target/$host_name/g;
1459                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1460                                 &update_jobdb_status_for_send_msgs($answer, $error);
1461                                 $found_ip_flag++ ;
1462                             }
1463                         }
1464                     }
1465                     if( $found_ip_flag == 0) {
1466                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1467                     }
1469                 # Answer is for one specific host   
1470                 } else {
1471                     # get encrypt_key
1472                     my $encrypt_key = &get_encrypt_key($answer_target);
1473                     if( not defined $encrypt_key ) {
1474                         # unknown target
1475                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1476                         next;
1477                     }
1478                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1479                     &update_jobdb_status_for_send_msgs($answer, $error);
1480                 }
1481             }
1482         }
1483     }
1485     my $filter = POE::Filter::Reference->new();
1486     my %result = ( 
1487             status => "seems ok to me",
1488             answer => $client_answer,
1489             );
1491     my $output = $filter->put( [ \%result ] );
1492     print @$output;
1497 sub session_start {
1498     my ($kernel) = $_[KERNEL];
1499     $global_kernel = $kernel;
1500     $kernel->yield('register_at_foreign_servers');
1501         $kernel->yield('create_fai_server_db', $fai_server_tn );
1502         $kernel->yield('create_fai_release_db', $fai_release_tn );
1503     $kernel->yield('watch_for_next_tasks');
1504         $kernel->sig(USR1 => "sig_handler");
1505         $kernel->sig(USR2 => "recreate_packages_db");
1506         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1507         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1508     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1509         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1510     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1511         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1512     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1514     # Start opsi check
1515     if ($opsi_enabled eq "true") {
1516         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1517     }
1522 sub watch_for_done_jobs {
1523     #CHECK: $heap for what?
1524     my ($kernel,$heap) = @_[KERNEL, HEAP];
1526     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1527         my $res = $job_db->select_dbentry( $sql_statement );
1529     while( my ($id, $hit) = each %{$res} ) {
1530         my $jobdb_id = $hit->{id};
1531         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1532         my $res = $job_db->del_dbentry($sql_statement); 
1533     }
1535     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1539 sub watch_for_opsi_jobs {
1540     my ($kernel) = $_[KERNEL];
1542     # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a 
1543     # opsi install job is to parse the xml message. There is still the correct header.
1544     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1545         my $res = $job_db->select_dbentry( $sql_statement );
1547     # Ask OPSI for an update of the running jobs
1548     while (my ($id, $hit) = each %$res ) {
1549         # Determine current parameters of the job
1550         my $hostId = $hit->{'plainname'};
1551         my $macaddress = $hit->{'macaddress'};
1552         my $progress = $hit->{'progress'};
1554         my $result= {};
1555         
1556         # For hosts, only return the products that are or get installed
1557         my $callobj;
1558         $callobj = {
1559             method  => 'getProductStates_hash',
1560             params  => [ $hostId ],
1561             id  => 1,
1562         };
1563         
1564         my $hres = $opsi_client->call($opsi_url, $callobj);
1565         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1566         if (not &check_opsi_res($hres)) {
1567             my $htmp= $hres->result->{$hostId};
1568         
1569             # Check state != not_installed or action == setup -> load and add
1570             my $products= 0;
1571             my $installed= 0;
1572             my $installing = 0;
1573             my $error= 0;  
1574             my @installed_list;
1575             my @error_list;
1576             my $act_status = "none";
1577             foreach my $product (@{$htmp}){
1579                 if ($product->{'installationStatus'} ne "not_installed" or
1580                         $product->{'actionRequest'} eq "setup"){
1582                     # Increase number of products for this host
1583                     $products++;
1584         
1585                     if ($product->{'installationStatus'} eq "failed"){
1586                         $result->{$product->{'productId'}}= "error";
1587                         unshift(@error_list, $product->{'productId'});
1588                         $error++;
1589                     }
1590                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1591                         $result->{$product->{'productId'}}= "installed";
1592                         unshift(@installed_list, $product->{'productId'});
1593                         $installed++;
1594                     }
1595                     if ($product->{'installationStatus'} eq "installing"){
1596                         $result->{$product->{'productId'}}= "installing";
1597                         $installing++;
1598                         $act_status = "installing - ".$product->{'productId'};
1599                     }
1600                 }
1601             }
1602         
1603             # Estimate "rough" progress, avoid division by zero
1604             if ($products == 0) {
1605                 $result->{'progress'}= 0;
1606             } else {
1607                 $result->{'progress'}= int($installed * 100 / $products);
1608             }
1610             # Set updates in job queue
1611             if ((not $error) && (not $installing) && ($installed)) {
1612                 $act_status = "installed - ".join(", ", @installed_list);
1613             }
1614             if ($error) {
1615                 $act_status = "error - ".join(", ", @error_list);
1616             }
1617             if ($progress ne $result->{'progress'} ) {
1618                 # Updating progress and result 
1619                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1620                 my $update_res = $job_db->update_dbentry($update_statement);
1621             }
1622             if ($progress eq 100) { 
1623                 # Updateing status
1624                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1625                 if ($error) {
1626                     $done_statement .= "status='error'";
1627                 } else {
1628                     $done_statement .= "status='done'";
1629                 }
1630                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1631                 my $done_res = $job_db->update_dbentry($done_statement);
1632             }
1635         }
1636     }
1638     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1642 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1643 sub watch_for_modified_jobs {
1644     my ($kernel,$heap) = @_[KERNEL, HEAP];
1646     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1647     my $res = $job_db->select_dbentry( $sql_statement );
1648     
1649     # if db contains no jobs which should be update, do nothing
1650     if (keys %$res != 0) {
1652         if ($job_synchronization  eq "true") {
1653             # make out of the db result a gosa-si message   
1654             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1655  
1656             # update all other SI-server
1657             &inform_all_other_si_server($update_msg);
1658         }
1660         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1661         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1662         $res = $job_db->update_dbentry($sql_statement);
1663     }
1665     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1669 sub watch_for_new_jobs {
1670         if($watch_for_new_jobs_in_progress == 0) {
1671                 $watch_for_new_jobs_in_progress = 1;
1672                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1674                 # check gosa job quaeue for jobs with executable timestamp
1675                 my $timestamp = &get_time();
1676                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1677                 my $res = $job_db->exec_statement( $sql_statement );
1679                 # Merge all new jobs that would do the same actions
1680                 my @drops;
1681                 my $hits;
1682                 foreach my $hit (reverse @{$res} ) {
1683                         my $macaddress= lc @{$hit}[8];
1684                         my $headertag= @{$hit}[5];
1685                         if(
1686                                 defined($hits->{$macaddress}) &&
1687                                 defined($hits->{$macaddress}->{$headertag}) &&
1688                                 defined($hits->{$macaddress}->{$headertag}[0])
1689                         ) {
1690                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1691                         }
1692                         $hits->{$macaddress}->{$headertag}= $hit;
1693                 }
1695                 # Delete new jobs with a matching job in state 'processing'
1696                 foreach my $macaddress (keys %{$hits}) {
1697                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1698                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1699                                 if(defined($jobdb_id)) {
1700                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1701                                         my $res = $job_db->exec_statement( $sql_statement );
1702                                         foreach my $hit (@{$res}) {
1703                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1704                                         }
1705                                 } else {
1706                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1707                                 }
1708                         }
1709                 }
1711                 # Commit deletion
1712                 $job_db->exec_statementlist(\@drops);
1714                 # Look for new jobs that could be executed
1715                 foreach my $macaddress (keys %{$hits}) {
1717                         # Look if there is an executing job
1718                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1719                         my $res = $job_db->exec_statement( $sql_statement );
1721                         # Skip new jobs for host if there is a processing job
1722                         if(defined($res) and defined @{$res}[0]) {
1723                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1724                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1725                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1726                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1727                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1728                                         if(defined($res_2) and defined @{$res_2}[0]) {
1729                                                 # Set status from goto-activation to 'waiting' and update timestamp
1730                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1731                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1732                                         }
1733                                 }
1734                                 next;
1735                         }
1737                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1738                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1739                                 if(defined($jobdb_id)) {
1740                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1742                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1743                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1744                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1746                                         # expect macaddress is unique!!!!!!
1747                                         my $target = $res_hash->{1}->{hostname};
1749                                         # change header
1750                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1752                                         # add sqlite_id
1753                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1755                                         $job_msg =~ /<header>(\S+)<\/header>/;
1756                                         my $header = $1 ;
1757                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1759                                         # update status in job queue to 'processing'
1760                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1761                                         my $res = $job_db->update_dbentry($sql_statement);
1762 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1764                                         # We don't want parallel processing
1765                                         last;
1766                                 }
1767                         }
1768                 }
1770                 $watch_for_new_jobs_in_progress = 0;
1771                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1772         }
1776 sub watch_for_new_messages {
1777     my ($kernel,$heap) = @_[KERNEL, HEAP];
1778     my @coll_user_msg;   # collection list of outgoing messages
1779     
1780     # check messaging_db for new incoming messages with executable timestamp
1781     my $timestamp = &get_time();
1782     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1783     my $res = $messaging_db->exec_statement( $sql_statement );
1784         foreach my $hit (@{$res}) {
1786         # create outgoing messages
1787         my $message_to = @{$hit}[3];
1788         # translate message_to to plain login name
1789         my @message_to_l = split(/,/, $message_to);  
1790                 my %receiver_h; 
1791                 foreach my $receiver (@message_to_l) {
1792                         if ($receiver =~ /^u_([\s\S]*)$/) {
1793                                 $receiver_h{$1} = 0;
1794                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1795                                 my $group_name = $1;
1796                                 # fetch all group members from ldap and add them to receiver hash
1797                                 my $ldap_handle = &get_ldap_handle();
1798                                 if (defined $ldap_handle) {
1799                                                 my $mesg = $ldap_handle->search(
1800                                                                                 base => $ldap_base,
1801                                                                                 scope => 'sub',
1802                                                                                 attrs => ['memberUid'],
1803                                                                                 filter => "cn=$group_name",
1804                                                                                 );
1805                                                 if ($mesg->count) {
1806                                                                 my @entries = $mesg->entries;
1807                                                                 foreach my $entry (@entries) {
1808                                                                                 my @receivers= $entry->get_value("memberUid");
1809                                                                                 foreach my $receiver (@receivers) { 
1810                                                                                                 $receiver_h{$1} = 0;
1811                                                                                 }
1812                                                                 }
1813                                                 } 
1814                                                 # translating errors ?
1815                                                 if ($mesg->code) {
1816                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1817                                                 }
1818                                 # ldap handle error ?           
1819                                 } else {
1820                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1821                                 }
1822                         } else {
1823                                 my $sbjct = &encode_base64(@{$hit}[1]);
1824                                 my $msg = &encode_base64(@{$hit}[7]);
1825                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1826                         }
1827                 }
1828                 my @receiver_l = keys(%receiver_h);
1830         my $message_id = @{$hit}[0];
1832         #add each outgoing msg to messaging_db
1833         my $receiver;
1834         foreach $receiver (@receiver_l) {
1835             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1836                 "VALUES ('".
1837                 $message_id."', '".    # id
1838                 @{$hit}[1]."', '".     # subject
1839                 @{$hit}[2]."', '".     # message_from
1840                 $receiver."', '".      # message_to
1841                 "none"."', '".         # flag
1842                 "out"."', '".          # direction
1843                 @{$hit}[6]."', '".     # delivery_time
1844                 @{$hit}[7]."', '".     # message
1845                 $timestamp."'".     # timestamp
1846                 ")";
1847             &daemon_log("M DEBUG: $sql_statement", 1);
1848             my $res = $messaging_db->exec_statement($sql_statement);
1849             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1850         }
1852         # set incoming message to flag d=deliverd
1853         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1854         &daemon_log("M DEBUG: $sql_statement", 7);
1855         $res = $messaging_db->update_dbentry($sql_statement);
1856         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1857     }
1859     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1860     return;
1863 sub watch_for_delivery_messages {
1864     my ($kernel, $heap) = @_[KERNEL, HEAP];
1866     # select outgoing messages
1867     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1868     #&daemon_log("0 DEBUG: $sql", 7);
1869     my $res = $messaging_db->exec_statement( $sql_statement );
1870     
1871     # build out msg for each    usr
1872     foreach my $hit (@{$res}) {
1873         my $receiver = @{$hit}[3];
1874         my $msg_id = @{$hit}[0];
1875         my $subject = @{$hit}[1];
1876         my $message = @{$hit}[7];
1878         # resolve usr -> host where usr is logged in
1879         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1880         #&daemon_log("0 DEBUG: $sql", 7);
1881         my $res = $login_users_db->exec_statement($sql);
1883         # reciver is logged in nowhere
1884         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1886                 my $send_succeed = 0;
1887                 foreach my $hit (@$res) {
1888                                 my $receiver_host = @$hit[0];
1889                 my $delivered2host = 0;
1890                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1892                                 # Looking for host in know_clients_db 
1893                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1894                                 my $res = $known_clients_db->exec_statement($sql);
1896                 # Host is known in known_clients_db
1897                 if (ref(@$res[0]) eq "ARRAY") {
1898                     my $receiver_key = @{@{$res}[0]}[2];
1899                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1900                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1901                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1902                     if ($error == 0 ) {
1903                         $send_succeed++ ;
1904                         $delivered2host++ ;
1905                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1906                     } else {
1907                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1908                     }
1909                 }
1910                 
1911                 # Message already send, do not need to do anything more, otherwise ...
1912                 if ($delivered2host) { next;}
1913     
1914                 # ...looking for host in foreign_clients_db
1915                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1916                 $res = $foreign_clients_db->exec_statement($sql);
1917   
1918                                 # Host is known in foreign_clients_db 
1919                                 if (ref(@$res[0]) eq "ARRAY") { 
1920                     my $registration_server = @{@{$res}[0]}[2];
1921                     
1922                     # Fetch encryption key for registration server
1923                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1924                     my $res = $known_server_db->exec_statement($sql);
1925                     if (ref(@$res[0]) eq "ARRAY") { 
1926                         my $registration_server_key = @{@{$res}[0]}[3];
1927                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1928                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1929                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1930                         if ($error == 0 ) {
1931                             $send_succeed++ ;
1932                             $delivered2host++ ;
1933                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1934                         } else {
1935                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1936                         }
1938                     } else {
1939                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1940                                 "registrated at server '$registration_server', ".
1941                                 "but no data available in known_server_db ", 1); 
1942                     }
1943                 }
1944                 
1945                 if (not $delivered2host) {
1946                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1947                 }
1948                 }
1950                 if ($send_succeed) {
1951                                 # set outgoing msg at db to deliverd
1952                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1953                                 my $res = $messaging_db->exec_statement($sql); 
1954                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1955                 } else {
1956             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1957         }
1958         }
1960     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1961     return;
1965 sub watch_for_done_messages {
1966     my ($kernel,$heap) = @_[KERNEL, HEAP];
1968     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1969     #&daemon_log("0 DEBUG: $sql", 7);
1970     my $res = $messaging_db->exec_statement($sql); 
1972     foreach my $hit (@{$res}) {
1973         my $msg_id = @{$hit}[0];
1975         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1976         #&daemon_log("0 DEBUG: $sql", 7); 
1977         my $res = $messaging_db->exec_statement($sql);
1979         # not all usr msgs have been seen till now
1980         if ( ref(@$res[0]) eq "ARRAY") { next; }
1981         
1982         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1983         #&daemon_log("0 DEBUG: $sql", 7);
1984         $res = $messaging_db->exec_statement($sql);
1985     
1986     }
1988     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1989     return;
1993 sub watch_for_old_known_clients {
1994     my ($kernel,$heap) = @_[KERNEL, HEAP];
1996     my $sql_statement = "SELECT * FROM $known_clients_tn";
1997     my $res = $known_clients_db->select_dbentry( $sql_statement );
1999     my $act_time = int(&get_time());
2001     while ( my ($hit_num, $hit) = each %$res) {
2002         my $expired_timestamp = int($hit->{'timestamp'});
2003         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2004         my $dt = DateTime->new( year   => $1,
2005                 month  => $2,
2006                 day    => $3,
2007                 hour   => $4,
2008                 minute => $5,
2009                 second => $6,
2010                 );
2012         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2013         $expired_timestamp = $dt->ymd('').$dt->hms('');
2014         if ($act_time > $expired_timestamp) {
2015             my $hostname = $hit->{'hostname'};
2016             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2017             my $del_res = $known_clients_db->exec_statement($del_sql);
2019             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2020         }
2022     }
2024     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2028 sub watch_for_next_tasks {
2029     my ($kernel,$heap) = @_[KERNEL, HEAP];
2031     my $sql = "SELECT * FROM $incoming_tn";
2032     my $res = $incoming_db->select_dbentry($sql);
2034     while ( my ($hit_num, $hit) = each %$res) {
2035         my $headertag = $hit->{'headertag'};
2036         if ($headertag =~ /^answer_(\d+)/) {
2037             # do not start processing, this message is for a still running POE::Wheel
2038             next;
2039         }
2040         my $message_id = $hit->{'id'};
2041         $kernel->yield('next_task', $hit);
2043         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2044         my $res = $incoming_db->exec_statement($sql);
2045     }
2047     $kernel->delay_set('watch_for_next_tasks', 0.1); 
2051 sub get_ldap_handle {
2052         my ($session_id) = @_;
2053         my $heap;
2054         my $ldap_handle;
2056         if (not defined $session_id ) { $session_id = 0 };
2057         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2059         if ($session_id == 0) {
2060                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2061                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2062                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
2064         } else {
2065                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2066                 if( defined $session_reference ) {
2067                         $heap = $session_reference->get_heap();
2068                 }
2070                 if (not defined $heap) {
2071                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2072                         return;
2073                 }
2075                 # TODO: This "if" is nonsense, because it doesn't prove that the
2076                 #       used handle is still valid - or if we've to reconnect...
2077                 #if (not exists $heap->{ldap_handle}) {
2078                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2079                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
2080                         $heap->{ldap_handle} = $ldap_handle;
2081                 #}
2082         }
2083         return $ldap_handle;
2087 sub change_fai_state {
2088     my ($st, $targets, $session_id) = @_;
2089     $session_id = 0 if not defined $session_id;
2090     # Set FAI state to localboot
2091     my %mapActions= (
2092         reboot    => '',
2093         update    => 'softupdate',
2094         localboot => 'localboot',
2095         reinstall => 'install',
2096         rescan    => '',
2097         wake      => '',
2098         memcheck  => 'memcheck',
2099         sysinfo   => 'sysinfo',
2100         install   => 'install',
2101     );
2103     # Return if this is unknown
2104     if (!exists $mapActions{ $st }){
2105         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2106       return;
2107     }
2109     my $state= $mapActions{ $st };
2111     my $ldap_handle = &get_ldap_handle($session_id);
2112     if( defined($ldap_handle) ) {
2114       # Build search filter for hosts
2115         my $search= "(&(objectClass=GOhard)";
2116         foreach (@{$targets}){
2117             $search.= "(macAddress=$_)";
2118         }
2119         $search.= ")";
2121       # If there's any host inside of the search string, procress them
2122         if (!($search =~ /macAddress/)){
2123             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2124             return;
2125         }
2127       # Perform search for Unit Tag
2128       my $mesg = $ldap_handle->search(
2129           base   => $ldap_base,
2130           scope  => 'sub',
2131           attrs  => ['dn', 'FAIstate', 'objectClass'],
2132           filter => "$search"
2133           );
2135           if ($mesg->count) {
2136                   my @entries = $mesg->entries;
2137                   if (0 == @entries) {
2138                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2139                   }
2141                   foreach my $entry (@entries) {
2142                           # Only modify entry if it is not set to '$state'
2143                           if ($entry->get_value("FAIstate") ne "$state"){
2144                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2145                                   my $result;
2146                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2147                                   if (exists $tmp{'FAIobject'}){
2148                                           if ($state eq ''){
2149                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2150                                                           delete => [ FAIstate => [] ] ]);
2151                                           } else {
2152                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2153                                                           replace => [ FAIstate => $state ] ]);
2154                                           }
2155                                   } elsif ($state ne ''){
2156                                           $result= $ldap_handle->modify($entry->dn, changes => [
2157                                                   add     => [ objectClass => 'FAIobject' ],
2158                                                   add     => [ FAIstate => $state ] ]);
2159                                   }
2161                                   # Errors?
2162                                   if ($result->code){
2163                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2164                                   }
2165                           } else {
2166                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2167                           }  
2168                   }
2169           } else {
2170                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2171           }
2173     # if no ldap handle defined
2174     } else {
2175         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2176     }
2178         return;
2182 sub change_goto_state {
2183     my ($st, $targets, $session_id) = @_;
2184     $session_id = 0  if not defined $session_id;
2186     # Switch on or off?
2187     my $state= $st eq 'active' ? 'active': 'locked';
2189     my $ldap_handle = &get_ldap_handle($session_id);
2190     if( defined($ldap_handle) ) {
2192       # Build search filter for hosts
2193       my $search= "(&(objectClass=GOhard)";
2194       foreach (@{$targets}){
2195         $search.= "(macAddress=$_)";
2196       }
2197       $search.= ")";
2199       # If there's any host inside of the search string, procress them
2200       if (!($search =~ /macAddress/)){
2201         return;
2202       }
2204       # Perform search for Unit Tag
2205       my $mesg = $ldap_handle->search(
2206           base   => $ldap_base,
2207           scope  => 'sub',
2208           attrs  => ['dn', 'gotoMode'],
2209           filter => "$search"
2210           );
2212       if ($mesg->count) {
2213         my @entries = $mesg->entries;
2214         foreach my $entry (@entries) {
2216           # Only modify entry if it is not set to '$state'
2217           if ($entry->get_value("gotoMode") ne $state){
2219             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2220             my $result;
2221             $result= $ldap_handle->modify($entry->dn, changes => [
2222                                                 replace => [ gotoMode => $state ] ]);
2224             # Errors?
2225             if ($result->code){
2226               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2227             }
2229           }
2230         }
2231       } else {
2232                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2233           }
2235     }
2239 sub run_recreate_packages_db {
2240     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2241     my $session_id = $session->ID;
2242         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2243         $kernel->yield('create_fai_release_db', $fai_release_tn);
2244         $kernel->yield('create_fai_server_db', $fai_server_tn);
2245         return;
2249 sub run_create_fai_server_db {
2250     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2251     my $session_id = $session->ID;
2252     my $task = POE::Wheel::Run->new(
2253             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2254             StdoutEvent  => "session_run_result",
2255             StderrEvent  => "session_run_debug",
2256             CloseEvent   => "session_run_done",
2257             );
2259     $heap->{task}->{ $task->ID } = $task;
2260     return;
2264 sub create_fai_server_db {
2265     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2266         my $result;
2268         if (not defined $session_id) { $session_id = 0; }
2269     my $ldap_handle = &get_ldap_handle();
2270         if(defined($ldap_handle)) {
2271                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2272                 my $mesg= $ldap_handle->search(
2273                         base   => $ldap_base,
2274                         scope  => 'sub',
2275                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2276                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2277                 );
2278                 if($mesg->{'resultCode'} == 0 &&
2279                    $mesg->count != 0) {
2280                    foreach my $entry (@{$mesg->{entries}}) {
2281                            if($entry->exists('FAIrepository')) {
2282                                    # Add an entry for each Repository configured for server
2283                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2284                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2285                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2286                                                    $result= $fai_server_db->add_dbentry( { 
2287                                                                    table => $table_name,
2288                                                                    primkey => ['server', 'release', 'tag'],
2289                                                                    server => $tmp_url,
2290                                                                    release => $tmp_release,
2291                                                                    sections => $tmp_sections,
2292                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2293                                                            } );
2294                                            }
2295                                    }
2296                            }
2297                    }
2298                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2300                 # TODO: Find a way to post the 'create_packages_list_db' event
2301                 if(not defined($dont_create_packages_list)) {
2302                         &create_packages_list_db(undef, undef, $session_id);
2303                 }
2304         }       
2305     
2306     $ldap_handle->disconnect;
2307         return $result;
2311 sub run_create_fai_release_db {
2312     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2313         my $session_id = $session->ID;
2314     my $task = POE::Wheel::Run->new(
2315             Program => sub { &create_fai_release_db($table_name, $session_id) },
2316             StdoutEvent  => "session_run_result",
2317             StderrEvent  => "session_run_debug",
2318             CloseEvent   => "session_run_done",
2319             );
2321     $heap->{task}->{ $task->ID } = $task;
2322     return;
2326 sub create_fai_release_db {
2327         my ($table_name, $session_id) = @_;
2328         my $result;
2330     # used for logging
2331     if (not defined $session_id) { $session_id = 0; }
2333     my $ldap_handle = &get_ldap_handle();
2334         if(defined($ldap_handle)) {
2335                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2336                 my $mesg= $ldap_handle->search(
2337                         base   => $ldap_base,
2338                         scope  => 'sub',
2339                         attrs  => [],
2340                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2341                 );
2342                 if($mesg->{'resultCode'} == 0 &&
2343                         $mesg->count != 0) {
2344                         # Walk through all possible FAI container ou's
2345                         my @sql_list;
2346                         my $timestamp= &get_time();
2347                         foreach my $ou (@{$mesg->{entries}}) {
2348                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2349                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2350                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2351                                         if(@tmp_array) {
2352                                                 foreach my $entry (@tmp_array) {
2353                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2354                                                                 my $sql= 
2355                                                                 "INSERT INTO $table_name "
2356                                                                 ."(timestamp, release, class, type, state) VALUES ("
2357                                                                 .$timestamp.","
2358                                                                 ."'".$entry->{'release'}."',"
2359                                                                 ."'".$entry->{'class'}."',"
2360                                                                 ."'".$entry->{'type'}."',"
2361                                                                 ."'".$entry->{'state'}."')";
2362                                                                 push @sql_list, $sql;
2363                                                         }
2364                                                 }
2365                                         }
2366                                 }
2367                         }
2369                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2370                         if(@sql_list) {
2371                                 unshift @sql_list, "VACUUM";
2372                                 unshift @sql_list, "DELETE FROM $table_name";
2373                                 $fai_release_db->exec_statementlist(\@sql_list);
2374                         }
2375                         daemon_log("$session_id DEBUG: Done with inserting",7);
2376                 }
2377                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2378         }
2379     $ldap_handle->disconnect;
2380         return $result;
2383 sub get_fai_types {
2384         my $tmp_classes = shift || return undef;
2385         my @result;
2387         foreach my $type(keys %{$tmp_classes}) {
2388                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2389                         my $entry = {
2390                                 type => $type,
2391                                 state => $tmp_classes->{$type}[0],
2392                         };
2393                         push @result, $entry;
2394                 }
2395         }
2397         return @result;
2400 sub get_fai_state {
2401         my $result = "";
2402         my $tmp_classes = shift || return $result;
2404         foreach my $type(keys %{$tmp_classes}) {
2405                 if(defined($tmp_classes->{$type}[0])) {
2406                         $result = $tmp_classes->{$type}[0];
2407                         
2408                 # State is equal for all types in class
2409                         last;
2410                 }
2411         }
2413         return $result;
2416 sub resolve_fai_classes {
2417         my ($fai_base, $ldap_handle, $session_id) = @_;
2418         if (not defined $session_id) { $session_id = 0; }
2419         my $result;
2420         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2421         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2422         my $fai_classes;
2424         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2425         my $mesg= $ldap_handle->search(
2426                 base   => $fai_base,
2427                 scope  => 'sub',
2428                 attrs  => ['cn','objectClass','FAIstate'],
2429                 filter => $fai_filter,
2430         );
2431         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2433         if($mesg->{'resultCode'} == 0 &&
2434                 $mesg->count != 0) {
2435                 foreach my $entry (@{$mesg->{entries}}) {
2436                         if($entry->exists('cn')) {
2437                                 my $tmp_dn= $entry->dn();
2439                                 # Skip classname and ou dn parts for class
2440                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2442                                 # Skip classes without releases
2443                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2444                                         next;
2445                                 }
2447                                 my $tmp_cn= $entry->get_value('cn');
2448                                 my $tmp_state= $entry->get_value('FAIstate');
2450                                 my $tmp_type;
2451                                 # Get FAI type
2452                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2453                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2454                                                 $tmp_type= $oclass;
2455                                                 last;
2456                                         }
2457                                 }
2459                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2460                                         # A Subrelease
2461                                         my @sub_releases = split(/,/, $tmp_release);
2463                                         # Walk through subreleases and build hash tree
2464                                         my $hash;
2465                                         while(my $tmp_sub_release = pop @sub_releases) {
2466                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2467                                         }
2468                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2469                                 } else {
2470                                         # A branch, no subrelease
2471                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2472                                 }
2473                         } elsif (!$entry->exists('cn')) {
2474                                 my $tmp_dn= $entry->dn();
2475                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2477                                 # Skip classes without releases
2478                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2479                                         next;
2480                                 }
2482                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2483                                         # A Subrelease
2484                                         my @sub_releases= split(/,/, $tmp_release);
2486                                         # Walk through subreleases and build hash tree
2487                                         my $hash;
2488                                         while(my $tmp_sub_release = pop @sub_releases) {
2489                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2490                                         }
2491                                         # Remove the last two characters
2492                                         chop($hash);
2493                                         chop($hash);
2495                                         eval('$fai_classes->'.$hash.'= {}');
2496                                 } else {
2497                                         # A branch, no subrelease
2498                                         if(!exists($fai_classes->{$tmp_release})) {
2499                                                 $fai_classes->{$tmp_release} = {};
2500                                         }
2501                                 }
2502                         }
2503                 }
2505                 # The hash is complete, now we can honor the copy-on-write based missing entries
2506                 foreach my $release (keys %$fai_classes) {
2507                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2508                 }
2509         }
2510         return $result;
2513 sub apply_fai_inheritance {
2514        my $fai_classes = shift || return {};
2515        my $tmp_classes;
2517        # Get the classes from the branch
2518        foreach my $class (keys %{$fai_classes}) {
2519                # Skip subreleases
2520                if($class =~ /^ou=.*$/) {
2521                        next;
2522                } else {
2523                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2524                }
2525        }
2527        # Apply to each subrelease
2528        foreach my $subrelease (keys %{$fai_classes}) {
2529                if($subrelease =~ /ou=/) {
2530                        foreach my $tmp_class (keys %{$tmp_classes}) {
2531                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2532                                        $fai_classes->{$subrelease}->{$tmp_class} =
2533                                        deep_copy($tmp_classes->{$tmp_class});
2534                                } else {
2535                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2536                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2537                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2538                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2539                                                }
2540                                        }
2541                                }
2542                        }
2543                }
2544        }
2546        # Find subreleases in deeper levels
2547        foreach my $subrelease (keys %{$fai_classes}) {
2548                if($subrelease =~ /ou=/) {
2549                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2550                                if($subsubrelease =~ /ou=/) {
2551                                        apply_fai_inheritance($fai_classes->{$subrelease});
2552                                }
2553                        }
2554                }
2555        }
2557        return $fai_classes;
2560 sub get_fai_release_entries {
2561         my $tmp_classes = shift || return;
2562         my $parent = shift || "";
2563         my @result = shift || ();
2565         foreach my $entry (keys %{$tmp_classes}) {
2566                 if(defined($entry)) {
2567                         if($entry =~ /^ou=.*$/) {
2568                                 my $release_name = $entry;
2569                                 $release_name =~ s/ou=//g;
2570                                 if(length($parent)>0) {
2571                                         $release_name = $parent."/".$release_name;
2572                                 }
2573                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2574                                 foreach my $bufentry(@bufentries) {
2575                                         push @result, $bufentry;
2576                                 }
2577                         } else {
2578                                 my @types = get_fai_types($tmp_classes->{$entry});
2579                                 foreach my $type (@types) {
2580                                         push @result, 
2581                                         {
2582                                                 'class' => $entry,
2583                                                 'type' => $type->{'type'},
2584                                                 'release' => $parent,
2585                                                 'state' => $type->{'state'},
2586                                         };
2587                                 }
2588                         }
2589                 }
2590         }
2592         return @result;
2595 sub deep_copy {
2596         my $this = shift;
2597         if (not ref $this) {
2598                 $this;
2599         } elsif (ref $this eq "ARRAY") {
2600                 [map deep_copy($_), @$this];
2601         } elsif (ref $this eq "HASH") {
2602                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2603         } else { die "what type is $_?" }
2607 sub session_run_result {
2608     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2609     $kernel->sig(CHLD => "child_reap");
2612 sub session_run_debug {
2613     my $result = $_[ARG0];
2614     print STDERR "$result\n";
2617 sub session_run_done {
2618     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2619     delete $heap->{task}->{$task_id};
2623 sub create_sources_list {
2624         my $session_id = shift;
2625         my $ldap_handle = &main::get_ldap_handle;
2626         my $result="/tmp/gosa_si_tmp_sources_list";
2628         # Remove old file
2629         if(stat($result)) {
2630                 unlink($result);
2631                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2632         }
2634         my $fh;
2635         open($fh, ">$result");
2636         if (not defined $fh) {
2637                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2638                 return undef;
2639         }
2640         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2641                 my $mesg=$ldap_handle->search(
2642                         base    => $main::ldap_server_dn,
2643                         scope   => 'base',
2644                         attrs   => 'FAIrepository',
2645                         filter  => 'objectClass=FAIrepositoryServer'
2646                 );
2647                 if($mesg->count) {
2648                         foreach my $entry(@{$mesg->{'entries'}}) {
2649                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2650                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2651                                         my $line = "deb $server $release";
2652                                         $sections =~ s/,/ /g;
2653                                         $line.= " $sections";
2654                                         print $fh $line."\n";
2655                                 }
2656                         }
2657                 }
2658         } else {
2659                 if (defined $main::ldap_server_dn){
2660                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2661                 } else {
2662                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2663                 }
2664         }
2665         close($fh);
2667         return $result;
2671 sub run_create_packages_list_db {
2672     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2673         my $session_id = $session->ID;
2675         my $task = POE::Wheel::Run->new(
2676                                         Priority => +20,
2677                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2678                                         StdoutEvent  => "session_run_result",
2679                                         StderrEvent  => "session_run_debug",
2680                                         CloseEvent   => "session_run_done",
2681                                         );
2682         $heap->{task}->{ $task->ID } = $task;
2686 sub create_packages_list_db {
2687         my ($ldap_handle, $sources_file, $session_id) = @_;
2688         
2689         # it should not be possible to trigger a recreation of packages_list_db
2690         # while packages_list_db is under construction, so set flag packages_list_under_construction
2691         # which is tested befor recreation can be started
2692         if (-r $packages_list_under_construction) {
2693                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2694                 return;
2695         } else {
2696                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2697                 # set packages_list_under_construction to true
2698                 system("touch $packages_list_under_construction");
2699                 @packages_list_statements=();
2700         }
2702         if (not defined $session_id) { $session_id = 0; }
2703         if (not defined $ldap_handle) { 
2704                 $ldap_handle= &get_ldap_handle();
2706                 if (not defined $ldap_handle) {
2707                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2708                         unlink($packages_list_under_construction);
2709                         return;
2710                 }
2711         }
2712         if (not defined $sources_file) { 
2713                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2714                 $sources_file = &create_sources_list($session_id);
2715         }
2717         if (not defined $sources_file) {
2718                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2719                 unlink($packages_list_under_construction);
2720                 return;
2721         }
2723         my $line;
2725         open(CONFIG, "<$sources_file") or do {
2726                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2727                 unlink($packages_list_under_construction);
2728                 return;
2729         };
2731         # Read lines
2732         while ($line = <CONFIG>){
2733                 # Unify
2734                 chop($line);
2735                 $line =~ s/^\s+//;
2736                 $line =~ s/^\s+/ /;
2738                 # Strip comments
2739                 $line =~ s/#.*$//g;
2741                 # Skip empty lines
2742                 if ($line =~ /^\s*$/){
2743                         next;
2744                 }
2746                 # Interpret deb line
2747                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2748                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2749                         my $section;
2750                         foreach $section (split(' ', $sections)){
2751                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2752                         }
2753                 }
2754         }
2756         close (CONFIG);
2759         find(\&cleanup_and_extract, keys( %repo_dirs ));
2760         &main::strip_packages_list_statements();
2761         unshift @packages_list_statements, "VACUUM";
2762         $packages_list_db->exec_statementlist(\@packages_list_statements);
2763         unlink($packages_list_under_construction);
2764         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2765         return;
2768 # This function should do some intensive task to minimize the db-traffic
2769 sub strip_packages_list_statements {
2770     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2771         my @new_statement_list=();
2772         my $hash;
2773         my $insert_hash;
2774         my $update_hash;
2775         my $delete_hash;
2776         my $local_timestamp=get_time();
2778         foreach my $existing_entry (@existing_entries) {
2779                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2780         }
2782         foreach my $statement (@packages_list_statements) {
2783                 if($statement =~ /^INSERT/i) {
2784                         # Assign the values from the insert statement
2785                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2786                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2787                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2788                                 # If section or description has changed, update the DB
2789                                 if( 
2790                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2791                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2792                                 ) {
2793                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2794                                 }
2795                         } else {
2796                                 # Insert a non-existing entry to db
2797                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2798                         }
2799                 } elsif ($statement =~ /^UPDATE/i) {
2800                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2801                         /^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;
2802                         foreach my $distribution (keys %{$hash}) {
2803                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2804                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2805                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2806                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2807                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2808                                                 my $section;
2809                                                 my $description;
2810                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2811                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2812                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2813                                                 }
2814                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2815                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2816                                                 }
2817                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2818                                         }
2819                                 }
2820                         }
2821                 }
2822         }
2824         # TODO: Check for orphaned entries
2826         # unroll the insert_hash
2827         foreach my $distribution (keys %{$insert_hash}) {
2828                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2829                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2830                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2831                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2832                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2833                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2834                                 ."'$local_timestamp')";
2835                         }
2836                 }
2837         }
2839         # unroll the update hash
2840         foreach my $distribution (keys %{$update_hash}) {
2841                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2842                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2843                                 my $set = "";
2844                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2845                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2846                                 }
2847                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2848                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2849                                 }
2850                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2851                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2852                                 }
2853                                 if(defined($set) and length($set) > 0) {
2854                                         $set .= "timestamp = '$local_timestamp'";
2855                                 } else {
2856                                         next;
2857                                 }
2858                                 push @new_statement_list, 
2859                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2860                                         ." distribution = '$distribution'"
2861                                         ." AND package = '$package'"
2862                                         ." AND version = '$version'";
2863                         }
2864                 }
2865         }
2867         @packages_list_statements = @new_statement_list;
2871 sub parse_package_info {
2872     my ($baseurl, $dist, $section, $session_id)= @_;
2873     my ($package);
2874     if (not defined $session_id) { $session_id = 0; }
2875     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2876     $repo_dirs{ "${repo_path}/pool" } = 1;
2878     foreach $package ("Packages.gz"){
2879         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2880         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2881         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2882     }
2883     
2887 sub get_package {
2888     my ($url, $dest, $session_id)= @_;
2889     if (not defined $session_id) { $session_id = 0; }
2891     my $tpath = dirname($dest);
2892     -d "$tpath" || mkpath "$tpath";
2894     # This is ugly, but I've no time to take a look at "how it works in perl"
2895     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2896         system("gunzip -cd '$dest' > '$dest.in'");
2897         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2898         unlink($dest);
2899         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2900     } else {
2901         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2902     }
2903     return 0;
2907 sub parse_package {
2908     my ($path, $dist, $srv_path, $session_id)= @_;
2909     if (not defined $session_id) { $session_id = 0;}
2910     my ($package, $version, $section, $description);
2911     my $PACKAGES;
2912     my $timestamp = &get_time();
2914     if(not stat("$path.in")) {
2915         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2916         return;
2917     }
2919     open($PACKAGES, "<$path.in");
2920     if(not defined($PACKAGES)) {
2921         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2922         return;
2923     }
2925     # Read lines
2926     while (<$PACKAGES>){
2927         my $line = $_;
2928         # Unify
2929         chop($line);
2931         # Use empty lines as a trigger
2932         if ($line =~ /^\s*$/){
2933             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2934             push(@packages_list_statements, $sql);
2935             $package = "none";
2936             $version = "none";
2937             $section = "none";
2938             $description = "none"; 
2939             next;
2940         }
2942         # Trigger for package name
2943         if ($line =~ /^Package:\s/){
2944             ($package)= ($line =~ /^Package: (.*)$/);
2945             next;
2946         }
2948         # Trigger for version
2949         if ($line =~ /^Version:\s/){
2950             ($version)= ($line =~ /^Version: (.*)$/);
2951             next;
2952         }
2954         # Trigger for description
2955         if ($line =~ /^Description:\s/){
2956             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2957             next;
2958         }
2960         # Trigger for section
2961         if ($line =~ /^Section:\s/){
2962             ($section)= ($line =~ /^Section: (.*)$/);
2963             next;
2964         }
2966         # Trigger for filename
2967         if ($line =~ /^Filename:\s/){
2968             my ($filename) = ($line =~ /^Filename: (.*)$/);
2969             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2970             next;
2971         }
2972     }
2974     close( $PACKAGES );
2975     unlink( "$path.in" );
2979 sub store_fileinfo {
2980     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2982     my %fileinfo = (
2983         'package' => $package,
2984         'dist' => $dist,
2985         'version' => $vers,
2986     );
2988     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2992 sub cleanup_and_extract {
2993     my $fileinfo = $repo_files{ $File::Find::name };
2995     if( defined $fileinfo ) {
2997         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2998         my $sql;
2999         my $package = $fileinfo->{ 'package' };
3000         my $newver = $fileinfo->{ 'version' };
3002         mkpath($dir);
3003         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3005                 if( -f "$dir/DEBIAN/templates" ) {
3007                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3009                         my $tmpl= "";
3010                         {
3011                                 local $/=undef;
3012                                 open FILE, "$dir/DEBIAN/templates";
3013                                 $tmpl = &encode_base64(<FILE>);
3014                                 close FILE;
3015                         }
3016                         rmtree("$dir/DEBIAN/templates");
3018                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3019                 push @packages_list_statements, $sql;
3020                 }
3021     }
3023     return;
3027 sub register_at_foreign_servers {   
3028     my ($kernel) = $_[KERNEL];
3030     # hole alle bekannten server aus known_server_db
3031     my $server_sql = "SELECT * FROM $known_server_tn";
3032     my $server_res = $known_server_db->exec_statement($server_sql);
3034     # no entries in known_server_db
3035     if (not ref(@$server_res[0]) eq "ARRAY") { 
3036         # TODO
3037     }
3039     # detect already connected clients
3040     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3041     my $client_res = $known_clients_db->exec_statement($client_sql);
3043     # send my server details to all other gosa-si-server within the network
3044     foreach my $hit (@$server_res) {
3045         my $hostname = @$hit[0];
3046         my $hostkey = &create_passwd;
3048         # add already connected clients to registration message 
3049         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3050         &add_content2xml_hash($myhash, 'key', $hostkey);
3051         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3053         # add locally loaded gosa-si modules to registration message
3054         my $loaded_modules = {};
3055         while (my ($package, $pck_info) = each %$known_modules) {
3056                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3057                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3058                                                         $loaded_modules->{$act_module} = ""; 
3059                                                 }
3060         }
3062         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3064         # add macaddress to registration message
3065         my ($host_ip, $host_port) = split(/:/, $hostname);
3066         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3067         my $network_interface= &get_interface_for_ip($local_ip);
3068         my $host_mac = &get_mac_for_interface($network_interface);
3069         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3070         
3071         # build registration message and send it
3072         my $foreign_server_msg = &create_xml_string($myhash);
3073         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3074     }
3075     
3076     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3077     return;
3081 #==== MAIN = main ==============================================================
3082 #  parse commandline options
3083 Getopt::Long::Configure( "bundling" );
3084 GetOptions("h|help" => \&usage,
3085         "c|config=s" => \$cfg_file,
3086         "f|foreground" => \$foreground,
3087         "v|verbose+" => \$verbose,
3088         "no-arp+" => \$no_arp,
3089            );
3091 #  read and set config parameters
3092 &check_cmdline_param ;
3093 &read_configfile($cfg_file, %cfg_defaults);
3094 &check_pid;
3096 $SIG{CHLD} = 'IGNORE';
3098 # forward error messages to logfile
3099 if( ! $foreground ) {
3100   open( STDIN,  '+>/dev/null' );
3101   open( STDOUT, '+>&STDIN'    );
3102   open( STDERR, '+>&STDIN'    );
3105 # Just fork, if we are not in foreground mode
3106 if( ! $foreground ) { 
3107     chdir '/'                 or die "Can't chdir to /: $!";
3108     $pid = fork;
3109     setsid                    or die "Can't start a new session: $!";
3110     umask 0;
3111 } else { 
3112     $pid = $$; 
3115 # Do something useful - put our PID into the pid_file
3116 if( 0 != $pid ) {
3117     open( LOCK_FILE, ">$pid_file" );
3118     print LOCK_FILE "$pid\n";
3119     close( LOCK_FILE );
3120     if( !$foreground ) { 
3121         exit( 0 ) 
3122     };
3125 # parse head url and revision from svn
3126 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3127 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3128 $server_headURL = defined $1 ? $1 : 'unknown' ;
3129 $server_revision = defined $2 ? $2 : 'unknown' ;
3130 if ($server_headURL =~ /\/tag\// || 
3131         $server_headURL =~ /\/branches\// ) {
3132     $server_status = "stable"; 
3133 } else {
3134     $server_status = "developmental" ;
3137 # Prepare log file
3138 my $root_uid = getpwnam('root');
3139 my $adm_gid = getgrnam('adm');
3140 chmod(0640, $log_file);
3141 chown($root_uid, $adm_gid, $log_file);
3144 daemon_log(" ", 1);
3145 daemon_log("$0 started!", 1);
3146 daemon_log("status: $server_status", 1);
3147 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3149 # connect to incoming_db
3150 unlink($incoming_file_name);
3151 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3152 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3154 # connect to gosa-si job queue
3155 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3156 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3158 # connect to known_clients_db
3159 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3160 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3162 # connect to foreign_clients_db
3163 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3164 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3166 # connect to known_server_db
3167 unlink($known_server_file_name);
3168 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3169 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3171 # connect to login_usr_db
3172 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3173 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3175 # connect to fai_server_db and fai_release_db
3176 unlink($fai_server_file_name);
3177 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3178 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3180 unlink($fai_release_file_name);
3181 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3182 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3184 # connect to packages_list_db
3185 #unlink($packages_list_file_name);
3186 unlink($packages_list_under_construction);
3187 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3188 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3190 # connect to messaging_db
3191 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3192 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3195 # create xml object used for en/decrypting
3196 $xml = new XML::Simple();
3199 # foreign servers 
3200 my @foreign_server_list;
3202 # add foreign server from cfg file
3203 if ($foreign_server_string ne "") {
3204     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3205     foreach my $foreign_server (@cfg_foreign_server_list) {
3206         push(@foreign_server_list, $foreign_server);
3207     }
3209     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3212 # Perform a DNS lookup for server registration if flag is true
3213 if ($dns_lookup eq "true") {
3214     # Add foreign server from dns
3215     my @tmp_servers;
3216     if (not $server_domain) {
3217         # Try our DNS Searchlist
3218         for my $domain(get_dns_domains()) {
3219             chomp($domain);
3220             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3221             if(@$tmp_domains) {
3222                 for my $tmp_server(@$tmp_domains) {
3223                     push @tmp_servers, $tmp_server;
3224                 }
3225             }
3226         }
3227         if(@tmp_servers && length(@tmp_servers)==0) {
3228             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3229         }
3230     } else {
3231         @tmp_servers = &get_server_addresses($server_domain);
3232         if( 0 == @tmp_servers ) {
3233             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3234         }
3235     }
3237     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3239     foreach my $server (@tmp_servers) { 
3240         unshift(@foreign_server_list, $server); 
3241     }
3242 } else {
3243     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3247 # eliminate duplicate entries
3248 @foreign_server_list = &del_doubles(@foreign_server_list);
3249 my $all_foreign_server = join(", ", @foreign_server_list);
3250 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3252 # add all found foreign servers to known_server
3253 my $act_timestamp = &get_time();
3254 foreach my $foreign_server (@foreign_server_list) {
3256         # do not add myself to known_server_db
3257         if (&is_local($foreign_server)) { next; }
3258         ######################################
3260     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3261             primkey=>['hostname'],
3262             hostname=>$foreign_server,
3263             macaddress=>"",
3264             status=>'not_jet_registered',
3265             hostkey=>"none",
3266             loaded_modules => "none", 
3267             timestamp=>$act_timestamp,
3268             } );
3272 # Import all modules
3273 &import_modules;
3275 # Check wether all modules are gosa-si valid passwd check
3276 &password_check;
3278 # Prepare for using Opsi 
3279 if ($opsi_enabled eq "true") {
3280     use JSON::RPC::Client;
3281     use XML::Quote qw(:all);
3282     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3283     $opsi_client = new JSON::RPC::Client;
3287 POE::Component::Server::TCP->new(
3288     Alias => "TCP_SERVER",
3289         Port => $server_port,
3290         ClientInput => sub {
3291         my ($kernel, $input) = @_[KERNEL, ARG0];
3292         push(@tasks, $input);
3293         push(@msgs_to_decrypt, $input);
3294         $kernel->yield("msg_to_decrypt");
3295         },
3296     InlineStates => {
3297         msg_to_decrypt => \&msg_to_decrypt,
3298         next_task => \&next_task,
3299         task_result => \&handle_task_result,
3300         task_done   => \&handle_task_done,
3301         task_debug  => \&handle_task_debug,
3302         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3303     }
3304 );
3306 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3308 # create session for repeatedly checking the job queue for jobs
3309 POE::Session->create(
3310         inline_states => {
3311                 _start => \&session_start,
3312         register_at_foreign_servers => \&register_at_foreign_servers,
3313         sig_handler => \&sig_handler,
3314         next_task => \&next_task,
3315         task_result => \&handle_task_result,
3316         task_done   => \&handle_task_done,
3317         task_debug  => \&handle_task_debug,
3318         watch_for_next_tasks => \&watch_for_next_tasks,
3319         watch_for_new_messages => \&watch_for_new_messages,
3320         watch_for_delivery_messages => \&watch_for_delivery_messages,
3321         watch_for_done_messages => \&watch_for_done_messages,
3322                 watch_for_new_jobs => \&watch_for_new_jobs,
3323         watch_for_modified_jobs => \&watch_for_modified_jobs,
3324         watch_for_done_jobs => \&watch_for_done_jobs,
3325         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3326         watch_for_old_known_clients => \&watch_for_old_known_clients,
3327         create_packages_list_db => \&run_create_packages_list_db,
3328         create_fai_server_db => \&run_create_fai_server_db,
3329         create_fai_release_db => \&run_create_fai_release_db,
3330                 recreate_packages_db => \&run_recreate_packages_db,
3331         session_run_result => \&session_run_result,
3332         session_run_debug => \&session_run_debug,
3333         session_run_done => \&session_run_done,
3334         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3335         }
3336 );
3339 POE::Kernel->run();
3340 exit;