Code

b0b4ffbda0538ff09ae0daddd10b52694cc07919
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($procid, $pid);
75 my ($arp_fifo);
76 my ($xml);
77 my $sources_list;
78 my $max_clients;
79 my %repo_files=();
80 my $repo_path;
81 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, 
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 );
93 # additional variable which should be globaly accessable
94 our $server_address;
95 our $server_mac_address;
96 our $gosa_address;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 our $known_modules;
104 # specifies the verbosity of the daemon_log
105 $verbose = 0 ;
107 # if foreground is not null, script will be not forked to background
108 $foreground = 0 ;
110 # specifies the timeout seconds while checking the online status of a registrating client
111 $ping_timeout = 5;
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
123         "timestamp DEFAULT 'none'", 
124         "headertag DEFAULT 'none'",
125                 "targettag DEFAULT 'none'",
126         "xmlmessage DEFAULT 'none'",
127         "module DEFAULT 'none'",
128         "sessionid DEFAULT '0'",
129         );
131 # holds all gosa jobs
132 our $job_db;
133 our $job_queue_tn = 'jobs';
134 my $job_queue_file_name;
135 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
136                 "timestamp DEFAULT 'none'", 
137                 "status DEFAULT 'none'", 
138                 "result DEFAULT 'none'", 
139                 "progress DEFAULT 'none'", 
140         "headertag DEFAULT 'none'", 
141                 "targettag DEFAULT 'none'", 
142                 "xmlmessage DEFAULT 'none'", 
143                 "macaddress DEFAULT 'none'",
144                 "plainname DEFAULT 'none'",
145         "siserver DEFAULT 'none'",
146         "modified DEFAULT '0'",
147                 );
149 # holds all other gosa-si-server
150 our $known_server_db;
151 our $known_server_tn = "known_server";
152 my $known_server_file_name;
153 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
155 # holds all registrated clients
156 our $known_clients_db;
157 our $known_clients_tn = "known_clients";
158 my $known_clients_file_name;
159 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
161 # holds all registered clients at a foreign server
162 our $foreign_clients_db;
163 our $foreign_clients_tn = "foreign_clients"; 
164 my $foreign_clients_file_name;
165 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
167 # holds all logged in user at each client 
168 our $login_users_db;
169 our $login_users_tn = "login_users";
170 my $login_users_file_name;
171 my @login_users_col_names = ("client", "user", "timestamp");
173 # holds all fai server, the debian release and tag
174 our $fai_server_db;
175 our $fai_server_tn = "fai_server"; 
176 my $fai_server_file_name;
177 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
179 our $fai_release_db;
180 our $fai_release_tn = "fai_release"; 
181 my $fai_release_file_name;
182 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
184 # holds all packages available from different repositories
185 our $packages_list_db;
186 our $packages_list_tn = "packages_list";
187 my $packages_list_file_name;
188 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
189 my $outdir = "/tmp/packages_list_db";
190 my $arch = "i386"; 
192 # holds all messages which should be delivered to a user
193 our $messaging_db;
194 our $messaging_tn = "messaging"; 
195 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
196         "flag", "direction", "delivery_time", "message", "timestamp" );
197 my $messaging_file_name;
199 # path to directory to store client install log files
200 our $client_fai_log_dir = "/var/log/fai"; 
202 # queue which stores taskes until one of the $max_children children are ready to process the task
203 my @tasks = qw();
204 my @msgs_to_decrypt = qw();
205 my $max_children = 2;
208 # loop delay for job queue to look for opsi jobs
209 my $job_queue_opsi_delay = 10;
210 our $opsi_client;
211 our $opsi_url;
212  
215 %cfg_defaults = (
216 "general" => {
217     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
218     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
219     },
220 "server" => {
221     "ip" => [\$server_ip, "0.0.0.0"],
222     "port" => [\$server_port, "20081"],
223     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
224     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
225     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
226     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
227     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
228     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
229     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
230     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
231     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
232     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
233     "repo-path"            => [\$repo_path, '/srv/www/repository'],
234     "ldap-uri"             => [\$ldap_uri, ""],
235     "ldap-base"            => [\$ldap_base, ""],
236     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
237     "ldap-admin-password"  => [\$ldap_admin_password, ""],
238     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
239     "max-clients"          => [\$max_clients, 10],
240     "wol-password"           => [\$wake_on_lan_passwd, ""],
241     },
242 "GOsaPackages" => {
243     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
244     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
245     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
246     "key" => [\$GosaPackages_key, "none"],
247     },
248 "ClientPackages" => {
249     "key" => [\$ClientPackages_key, "none"],
250     },
251 "ServerPackages"=> {
252     "address"      => [\$foreign_server_string, ""],
253     "domain"  => [\$server_domain, ""],
254     "key"     => [\$ServerPackages_key, "none"],
255     "key-lifetime" => [\$foreign_servers_register_delay, 120],
256     "job-synchronization-enabled" => [\$job_synchronization, "true"],
257     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
258     },
259 "ArpHandler" => {
260     "enabled"   => [\$arp_enabled, "true"],
261     "interface" => [\$arp_interface, "all"],
262         },
263 "Opsi" => {
264     "enabled"  => [\$opsi_enabled, "false"], 
265     "server"   => [\$opsi_server, "localhost"],
266     "admin"    => [\$opsi_admin, "opsi-admin"],
267     "password" => [\$opsi_password, "secret"],
268    },
270 );
273 #===  FUNCTION  ================================================================
274 #         NAME:  usage
275 #   PARAMETERS:  nothing
276 #      RETURNS:  nothing
277 #  DESCRIPTION:  print out usage text to STDERR
278 #===============================================================================
279 sub usage {
280     print STDERR << "EOF" ;
281 usage: $prg [-hvf] [-c config]
283            -h        : this (help) message
284            -c <file> : config file
285            -f        : foreground, process will not be forked to background
286            -v        : be verbose (multiple to increase verbosity)
287            -no-arp   : starts $prg without connection to arp module
288  
289 EOF
290     print "\n" ;
294 #===  FUNCTION  ================================================================
295 #         NAME:  logging
296 #   PARAMETERS:  level - string - default 'info'
297 #                msg - string -
298 #                facility - string - default 'LOG_DAEMON'
299 #      RETURNS:  nothing
300 #  DESCRIPTION:  function for logging
301 #===============================================================================
302 sub daemon_log {
303     # log into log_file
304     my( $msg, $level ) = @_;
305     if(not defined $msg) { return }
306     if(not defined $level) { $level = 1 }
307     if(defined $log_file){
308         open(LOG_HANDLE, ">>$log_file");
309         chmod 0600, $log_file;
310         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
311             print STDERR "cannot open $log_file: $!";
312             return 
313         }
314         chomp($msg);
315         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
316         if($level <= $verbose){
317             my ($seconds, $minutes, $hours, $monthday, $month,
318                     $year, $weekday, $yearday, $sommertime) = localtime(time);
319             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
320             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
321             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
322             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
323             $month = $monthnames[$month];
324             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
325             $year+=1900;
326             my $name = $prg;
328             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
329             print LOG_HANDLE $log_msg;
330             if( $foreground ) { 
331                 print STDERR $log_msg;
332             }
333         }
334         close( LOG_HANDLE );
335     }
339 #===  FUNCTION  ================================================================
340 #         NAME:  check_cmdline_param
341 #   PARAMETERS:  nothing
342 #      RETURNS:  nothing
343 #  DESCRIPTION:  validates commandline parameter
344 #===============================================================================
345 sub check_cmdline_param () {
346     my $err_config;
347     my $err_counter = 0;
348         if(not defined($cfg_file)) {
349                 $cfg_file = "/etc/gosa-si/server.conf";
350                 if(! -r $cfg_file) {
351                         $err_config = "please specify a config file";
352                         $err_counter += 1;
353                 }
354     }
355     if( $err_counter > 0 ) {
356         &usage( "", 1 );
357         if( defined( $err_config)) { print STDERR "$err_config\n"}
358         print STDERR "\n";
359         exit( -1 );
360     }
364 #===  FUNCTION  ================================================================
365 #         NAME:  check_pid
366 #   PARAMETERS:  nothing
367 #      RETURNS:  nothing
368 #  DESCRIPTION:  handels pid processing
369 #===============================================================================
370 sub check_pid {
371     $pid = -1;
372     # Check, if we are already running
373     if( open(LOCK_FILE, "<$pid_file") ) {
374         $pid = <LOCK_FILE>;
375         if( defined $pid ) {
376             chomp( $pid );
377             if( -f "/proc/$pid/stat" ) {
378                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
379                 if( $stat ) {
380                                         daemon_log("ERROR: Already running",1);
381                     close( LOCK_FILE );
382                     exit -1;
383                 }
384             }
385         }
386         close( LOCK_FILE );
387         unlink( $pid_file );
388     }
390     # create a syslog msg if it is not to possible to open PID file
391     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
392         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
393         if (open(LOCK_FILE, '<', $pid_file)
394                 && ($pid = <LOCK_FILE>))
395         {
396             chomp($pid);
397             $msg .= "(PID $pid)\n";
398         } else {
399             $msg .= "(unable to read PID)\n";
400         }
401         if( ! ($foreground) ) {
402             openlog( $0, "cons,pid", "daemon" );
403             syslog( "warning", $msg );
404             closelog();
405         }
406         else {
407             print( STDERR " $msg " );
408         }
409         exit( -1 );
410     }
413 #===  FUNCTION  ================================================================
414 #         NAME:  import_modules
415 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
416 #                are stored
417 #      RETURNS:  nothing
418 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
419 #                state is on is imported by "require 'file';"
420 #===============================================================================
421 sub import_modules {
422     daemon_log(" ", 1);
424     if (not -e $modules_path) {
425         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
426     }
428     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
429     while (defined (my $file = readdir (DIR))) {
430         if (not $file =~ /(\S*?).pm$/) {
431             next;
432         }
433                 my $mod_name = $1;
435         # ArpHandler switch
436         if( $file =~ /ArpHandler.pm/ ) {
437             if( $arp_enabled eq "false" ) { next; }
438         }
439         
440         eval { require $file; };
441         if ($@) {
442             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
443             daemon_log("$@", 5);
444                 } else {
445                         my $info = eval($mod_name.'::get_module_info()');
446                         # Only load module if get_module_info() returns a non-null object
447                         if( $info ) {
448                                 my ($input_address, $input_key, $event_hash) = @{$info};
449                                 $known_modules->{$mod_name} = $info;
450                                 daemon_log("0 INFO: module $mod_name loaded", 5);
451                         }
452                 }
453     }   
455     close (DIR);
458 #===  FUNCTION  ================================================================
459 #         NAME:  password_check
460 #   PARAMETERS:  nothing
461 #      RETURNS:  nothing
462 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
463 #                the same password
464 #===============================================================================
465 sub password_check {
466     my $passwd_hash = {};
467     while (my ($mod_name, $mod_info) = each %$known_modules) {
468         my $mod_passwd = @$mod_info[1];
469         if (not defined $mod_passwd) { next; }
470         if (not exists $passwd_hash->{$mod_passwd}) {
471             $passwd_hash->{$mod_passwd} = $mod_name;
473         # escalates critical error
474         } else {
475             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
476             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
477             exit( -1 );
478         }
479     }
484 #===  FUNCTION  ================================================================
485 #         NAME:  sig_int_handler
486 #   PARAMETERS:  signal - string - signal arose from system
487 #      RETURNS:  nothing
488 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
489 #===============================================================================
490 sub sig_int_handler {
491     my ($signal) = @_;
493 #       if (defined($ldap_handle)) {
494 #               $ldap_handle->disconnect;
495 #       }
496     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
497     
499     daemon_log("shutting down gosa-si-server", 1);
500     system("kill `ps -C gosa-si-server -o pid=`");
502 $SIG{INT} = \&sig_int_handler;
505 sub check_key_and_xml_validity {
506     my ($crypted_msg, $module_key, $session_id) = @_;
507     my $msg;
508     my $msg_hash;
509     my $error_string;
510     eval{
511         $msg = &decrypt_msg($crypted_msg, $module_key);
513         if ($msg =~ /<xml>/i){
514             $msg =~ s/\s+/ /g;  # just for better daemon_log
515             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
516             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
518             ##############
519             # check header
520             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
521             my $header_l = $msg_hash->{'header'};
522             if( 1 > @{$header_l} ) { die 'empty header tag'; }
523             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
524             my $header = @{$header_l}[0];
525             if( 0 == length $header) { die 'empty string in header tag'; }
527             ##############
528             # check source
529             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
530             my $source_l = $msg_hash->{'source'};
531             if( 1 > @{$source_l} ) { die 'empty source tag'; }
532             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
533             my $source = @{$source_l}[0];
534             if( 0 == length $source) { die 'source error'; }
536             ##############
537             # check target
538             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
539             my $target_l = $msg_hash->{'target'};
540             if( 1 > @{$target_l} ) { die 'empty target tag'; }
541         }
542     };
543     if($@) {
544         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
545         $msg = undef;
546         $msg_hash = undef;
547     }
549     return ($msg, $msg_hash);
553 sub check_outgoing_xml_validity {
554     my ($msg, $session_id) = @_;
556     my $msg_hash;
557     eval{
558         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
560         ##############
561         # check header
562         my $header_l = $msg_hash->{'header'};
563         if( 1 != @{$header_l} ) {
564             die 'no or more than one headers specified';
565         }
566         my $header = @{$header_l}[0];
567         if( 0 == length $header) {
568             die 'header has length 0';
569         }
571         ##############
572         # check source
573         my $source_l = $msg_hash->{'source'};
574         if( 1 != @{$source_l} ) {
575             die 'no or more than 1 sources specified';
576         }
577         my $source = @{$source_l}[0];
578         if( 0 == length $source) {
579             die 'source has length 0';
580         }
581         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582                 $source =~ /^GOSA$/i ) {
583             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
584         }
585         
586         ##############
587         # check target  
588         my $target_l = $msg_hash->{'target'};
589         if( 0 == @{$target_l} ) {
590             die "no targets specified";
591         }
592         foreach my $target (@$target_l) {
593             if( 0 == length $target) {
594                 die "target has length 0";
595             }
596             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
597                     $target =~ /^GOSA$/i ||
598                     $target =~ /^\*$/ ||
599                     $target =~ /KNOWN_SERVER/i ||
600                     $target =~ /JOBDB/i ||
601                     $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 ){
602                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
603             }
604         }
605     };
606     if($@) {
607         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
608         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
609         $msg_hash = undef;
610     }
612     return ($msg_hash);
616 sub input_from_known_server {
617     my ($input, $remote_ip, $session_id) = @_ ;  
618     my ($msg, $msg_hash, $module);
620     my $sql_statement= "SELECT * FROM known_server";
621     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
623     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
624         my $host_name = $hit->{hostname};
625         if( not $host_name =~ "^$remote_ip") {
626             next;
627         }
628         my $host_key = $hit->{hostkey};
629         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
630         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
632         # check if module can open msg envelope with module key
633         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
634         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
635             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
636             daemon_log("$@", 8);
637             next;
638         }
639         else {
640             $msg = $tmp_msg;
641             $msg_hash = $tmp_msg_hash;
642             $module = "ServerPackages";
643             last;
644         }
645     }
647     if( (!$msg) || (!$msg_hash) || (!$module) ) {
648         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
649     }
650   
651     return ($msg, $msg_hash, $module);
655 sub input_from_known_client {
656     my ($input, $remote_ip, $session_id) = @_ ;  
657     my ($msg, $msg_hash, $module);
659     my $sql_statement= "SELECT * FROM known_clients";
660     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
661     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
662         my $host_name = $hit->{hostname};
663         if( not $host_name =~ /^$remote_ip:\d*$/) {
664                 next;
665                 }
666         my $host_key = $hit->{hostkey};
667         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
668         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
670         # check if module can open msg envelope with module key
671         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
673         if( (!$msg) || (!$msg_hash) ) {
674             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
675             &daemon_log("$@", 8);
676             next;
677         }
678         else {
679             $module = "ClientPackages";
680             last;
681         }
682     }
684     if( (!$msg) || (!$msg_hash) || (!$module) ) {
685         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
686     }
688     return ($msg, $msg_hash, $module);
692 sub input_from_unknown_host {
693     no strict "refs";
694     my ($input, $session_id) = @_ ;
695     my ($msg, $msg_hash, $module);
696     my $error_string;
697     
698         my %act_modules = %$known_modules;
699         
700     while( my ($mod, $info) = each(%act_modules)) {
702         # check a key exists for this module
703         my $module_key = ${$mod."_key"};
704         if( not defined $module_key ) {
705             if( $mod eq 'ArpHandler' ) {
706                 next;
707             }
708             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
709             next;
710         }
711         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
713         # check if module can open msg envelope with module key
714         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
715         if( (not defined $msg) || (not defined $msg_hash) ) {
716             next;
717         }
718         else {
719             $module = $mod;
720             last;
721         }
722     }
724     if( (!$msg) || (!$msg_hash) || (!$module)) {
725         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
726     }
728     return ($msg, $msg_hash, $module);
732 sub create_ciphering {
733     my ($passwd) = @_;
734         if((!defined($passwd)) || length($passwd)==0) {
735                 $passwd = "";
736         }
737     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
738     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
739     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
740     $my_cipher->set_iv($iv);
741     return $my_cipher;
745 sub encrypt_msg {
746     my ($msg, $key) = @_;
747     my $my_cipher = &create_ciphering($key);
748     my $len;
749     {
750             use bytes;
751             $len= 16-length($msg)%16;
752     }
753     $msg = "\0"x($len).$msg;
754     $msg = $my_cipher->encrypt($msg);
755     chomp($msg = &encode_base64($msg));
756     # there are no newlines allowed inside msg
757     $msg=~ s/\n//g;
758     return $msg;
762 sub decrypt_msg {
764     my ($msg, $key) = @_ ;
765     $msg = &decode_base64($msg);
766     my $my_cipher = &create_ciphering($key);
767     $msg = $my_cipher->decrypt($msg); 
768     $msg =~ s/\0*//g;
769     return $msg;
773 sub get_encrypt_key {
774     my ($target) = @_ ;
775     my $encrypt_key;
776     my $error = 0;
778     # target can be in known_server
779     if( not defined $encrypt_key ) {
780         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
781         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
782         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
783             my $host_name = $hit->{hostname};
784             if( $host_name ne $target ) {
785                 next;
786             }
787             $encrypt_key = $hit->{hostkey};
788             last;
789         }
790     }
792     # target can be in known_client
793     if( not defined $encrypt_key ) {
794         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
795         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
796         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
797             my $host_name = $hit->{hostname};
798             if( $host_name ne $target ) {
799                 next;
800             }
801             $encrypt_key = $hit->{hostkey};
802             last;
803         }
804     }
806     return $encrypt_key;
810 #===  FUNCTION  ================================================================
811 #         NAME:  open_socket
812 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
813 #                [PeerPort] string necessary if port not appended by PeerAddr
814 #      RETURNS:  socket IO::Socket::INET
815 #  DESCRIPTION:  open a socket to PeerAddr
816 #===============================================================================
817 sub open_socket {
818     my ($PeerAddr, $PeerPort) = @_ ;
819     if(defined($PeerPort)){
820         $PeerAddr = $PeerAddr.":".$PeerPort;
821     }
822     my $socket;
823     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
824             Porto => "tcp",
825             Type => SOCK_STREAM,
826             Timeout => 5,
827             );
828     if(not defined $socket) {
829         return;
830     }
831 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
832     return $socket;
836 #sub get_local_ip_for_remote_ip {
837 #       my $remote_ip= shift;
838 #       my $result="0.0.0.0";
840 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
841 #               if($remote_ip eq "127.0.0.1") {
842 #                       $result = "127.0.0.1";
843 #               } else {
844 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
846 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
847 #                               or die "Could not open $PROC_NET_ROUTE";
849 #                       my @ifs = <PROC_NET_ROUTE>;
851 #                       close(PROC_NET_ROUTE);
853 #                       # Eat header line
854 #                       shift @ifs;
855 #                       chomp @ifs;
856 #                       foreach my $line(@ifs) {
857 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
858 #                               my $destination;
859 #                               my $mask;
860 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
861 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
862 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
863 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
864 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
865 #                                       # destination matches route, save mac and exit
866 #                                       $result= &get_ip($Iface);
867 #                                       last;
868 #                               }
869 #                       }
870 #               }
871 #       } else {
872 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
873 #       }
874 #       return $result;
875 #}
878 sub send_msg_to_target {
879     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
880     my $error = 0;
881     my $header;
882     my $timestamp = &get_time();
883     my $new_status;
884     my $act_status;
885     my ($sql_statement, $res);
886   
887     if( $msg_header ) {
888         $header = "'$msg_header'-";
889     } else {
890         $header = "";
891     }
893         # Patch the source ip
894         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
895                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
896                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
897         }
899     # encrypt xml msg
900     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
902     # opensocket
903     my $socket = &open_socket($address);
904     if( !$socket ) {
905         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
906         $error++;
907     }
908     
909     if( $error == 0 ) {
910         # send xml msg
911         print $socket $crypted_msg."\n";
913         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
914         daemon_log("$session_id DEBUG: message:\n$msg", 9);
915         
916     }
918     # close socket in any case
919     if( $socket ) {
920         close $socket;
921     }
923     if( $error > 0 ) { $new_status = "down"; }
924     else { $new_status = $msg_header; }
927     # known_clients
928     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
929     $res = $known_clients_db->select_dbentry($sql_statement);
930     if( keys(%$res) == 1) {
931         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
932         if ($act_status eq "down" && $new_status eq "down") {
933             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
934             $res = $known_clients_db->del_dbentry($sql_statement);
935             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
936         } else { 
937             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
938             $res = $known_clients_db->update_dbentry($sql_statement);
939             if($new_status eq "down"){
940                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
941             } else {
942                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
943             }
944         }
945     }
947     # known_server
948     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
949     $res = $known_server_db->select_dbentry($sql_statement);
950     if( keys(%$res) == 1) {
951         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
952         if ($act_status eq "down" && $new_status eq "down") {
953             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
954             $res = $known_server_db->del_dbentry($sql_statement);
955             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
956         } 
957         else { 
958             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
959             $res = $known_server_db->update_dbentry($sql_statement);
960             if($new_status eq "down"){
961                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
962             } else {
963                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
964             }
965         }
966     }
967     return $error; 
971 sub update_jobdb_status_for_send_msgs {
972     my ($answer, $error) = @_;
973     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
974         my $jobdb_id = $1;
975             
976         # sending msg faild
977         if( $error ) {
978             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
979                 my $sql_statement = "UPDATE $job_queue_tn ".
980                     "SET status='error', result='can not deliver msg, please consult log file' ".
981                     "WHERE id=$jobdb_id";
982                 my $res = $job_db->update_dbentry($sql_statement);
983             }
985         # sending msg was successful
986         } else {
987             my $sql_statement = "UPDATE $job_queue_tn ".
988                 "SET status='done' ".
989                 "WHERE id=$jobdb_id AND status='processed'";
990             my $res = $job_db->update_dbentry($sql_statement);
991         }
992     }
996 sub sig_handler {
997         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
998         daemon_log("0 INFO got signal '$signal'", 1); 
999         $kernel->sig_handled();
1000         return;
1004 sub msg_to_decrypt {
1005     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1006     my $session_id = $session->ID;
1007     my ($msg, $msg_hash, $module);
1008     my $error = 0;
1010     # hole neue msg aus @msgs_to_decrypt
1011     my $next_msg = shift @msgs_to_decrypt;
1012     
1013     # entschlüssle sie
1015     # msg is from a new client or gosa
1016     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1017     # msg is from a gosa-si-server
1018     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1019         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1020     }
1021     # msg is from a gosa-si-client
1022     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1023         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1024     }
1025     # an error occurred
1026     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1027         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1028         # could not understand a msg from its server the client cause a re-registering process
1029         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1030                 "' to cause a re-registering of the client if necessary", 3);
1031         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1032         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1033         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1034             my $host_name = $hit->{'hostname'};
1035             my $host_key = $hit->{'hostkey'};
1036             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1037             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1038             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1039         }
1040         $error++;
1041     }
1044     my $header;
1045     my $target;
1046     my $source;
1047     my $done = 0;
1048     my $sql;
1049     my $res;
1051     # check whether this message should be processed here
1052     if ($error == 0) {
1053         $header = @{$msg_hash->{'header'}}[0];
1054         $target = @{$msg_hash->{'target'}}[0];
1055         $source = @{$msg_hash->{'source'}}[0];
1056                 my $not_found_in_known_clients_db = 0;
1057                 my $not_found_in_known_server_db = 0;
1058                 my $not_found_in_foreign_clients_db = 0;
1059         my $local_address;
1060         my $local_mac;
1061         my ($target_ip, $target_port) = split(':', $target);
1062         
1063         # Determine the local ip address if target is an ip address
1064                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1065                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1066                 } else {
1067             $local_address = $server_address;
1068         }
1070         # Determine the local mac address if target is a mac address
1071         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) {
1072             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1073             my $network_interface= &get_interface_for_ip($loc_ip);
1074             $local_mac = &get_mac_for_interface($network_interface);
1075         } else {
1076             $local_mac = $server_mac_address;
1077         }
1079         # target and source is equal to GOSA -> process here
1080         if (not $done) {
1081             if ($target eq "GOSA" && $source eq "GOSA") {
1082                 $done = 1;                    
1083             }
1084         }
1086         # target is own address without forward_to_gosa-tag -> process here
1087         if (not $done) {
1088             #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1089             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1090                 $done = 1;
1091                 if ($source eq "GOSA") {
1092                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1093                 }
1094                 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1095             }
1096         }
1098         # target is a client address in known_clients -> process here
1099                 if (not $done) {
1100                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1101                                 $res = $known_clients_db->select_dbentry($sql);
1102                                 if (keys(%$res) > 0) {
1103                                                 $done = 1; 
1104                                                 my $hostname = $res->{1}->{'hostname'};
1105                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1106                         #print STDERR "target is a client address in known_clients -> process here\n";
1107                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1108                         if ($source eq "GOSA") {
1109                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1110                         }
1112                                 } else {
1113                                                 $not_found_in_known_clients_db = 1;
1114                                 }
1115                 }
1116         
1117         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1118         if (not $done) {
1119             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1120             my $gosa_at;
1121             my $gosa_session_id;
1122             if (($target eq $local_address) && (defined $forward_to_gosa)){
1123                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1124                 if ($gosa_at ne $local_address) {
1125                     $done = 1;
1126                     #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1127                 }
1128             }
1129         }
1131         # if message should be processed here -> add message to incoming_db
1132                 if ($done) {
1133                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1134                                 # so gosa-si-server knows how to process this kind of messages
1135                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1136                                                 $module = "GosaPackages";
1137                                 }
1139                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1140                                                                 primkey=>[],
1141                                                                 headertag=>$header,
1142                                                                 targettag=>$target,
1143                                                                 xmlmessage=>&encode_base64($msg),
1144                                                                 timestamp=>&get_time,
1145                                                                 module=>$module,
1146                                                                 sessionid=>$session_id,
1147                                                                 } );
1148                 }
1150         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1151         if (not $done) {
1152             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1153             my $gosa_at;
1154             my $gosa_session_id;
1155             if (($target eq $local_address) && (defined $forward_to_gosa)){
1156                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1157                 if ($gosa_at eq $local_address) {
1158                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1159                     if( defined $session_reference ) {
1160                         $heap = $session_reference->get_heap();
1161                     }
1162                     if(exists $heap->{'client'}) {
1163                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1164                         $heap->{'client'}->put($msg);
1165                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1166                     }
1167                     $done = 1;
1168                     #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1169                 }
1170             }
1172         }
1174         # target is a client address in foreign_clients -> forward to registration server
1175         if (not $done) {
1176             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1177             $res = $foreign_clients_db->select_dbentry($sql);
1178             if (keys(%$res) > 0) {
1179                     my $hostname = $res->{1}->{'hostname'};
1180                     my ($host_ip, $host_port) = split(/:/, $hostname);
1181                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1182                 my $regserver = $res->{1}->{'regserver'};
1183                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1184                 my $res = $known_server_db->select_dbentry($sql);
1185                 if (keys(%$res) > 0) {
1186                     my $regserver_key = $res->{1}->{'hostkey'};
1187                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1188                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1189                     if ($source eq "GOSA") {
1190                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1191                     }
1192                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1193                 }
1194                 $done = 1;
1195                 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1196             } else {
1197                                 $not_found_in_foreign_clients_db = 1;
1198                         }
1199         }
1201         # target is a server address -> forward to server
1202         if (not $done) {
1203             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1204             $res = $known_server_db->select_dbentry($sql);
1205             if (keys(%$res) > 0) {
1206                 my $hostkey = $res->{1}->{'hostkey'};
1208                 if ($source eq "GOSA") {
1209                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1210                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1212                 }
1214                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1215                 $done = 1;
1216             } else {
1217                                 $not_found_in_known_server_db = 1;
1218                         }
1219         }
1221                 
1222                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1223                 if ( $not_found_in_foreign_clients_db 
1224                                                 && $not_found_in_known_server_db
1225                                                 && $not_found_in_known_clients_db) {
1226                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1227                                                                 primkey=>[],
1228                                                                 headertag=>$header,
1229                                                                 targettag=>$target,
1230                                                                 xmlmessage=>&encode_base64($msg),
1231                                                                 timestamp=>&get_time,
1232                                                                 module=>$module,
1233                                                                 sessionid=>$session_id,
1234                                                                 } );
1235                                 $done = 1;
1236                 }
1239         if (not $done) {
1240             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1241             if ($source eq "GOSA") {
1242                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1243                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1245                 my $session_reference = $kernel->ID_id_to_session($session_id);
1246                 if( defined $session_reference ) {
1247                     $heap = $session_reference->get_heap();
1248                 }
1249                 if(exists $heap->{'client'}) {
1250                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1251                     $heap->{'client'}->put($error_msg);
1252                 }
1253             }
1254         }
1256     }
1258     return;
1262 sub next_task {
1263     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1264     my $running_task = POE::Wheel::Run->new(
1265             Program => sub { process_task($session, $heap, $task) },
1266             StdioFilter => POE::Filter::Reference->new(),
1267             StdoutEvent  => "task_result",
1268             StderrEvent  => "task_debug",
1269             CloseEvent   => "task_done",
1270             );
1271     $heap->{task}->{ $running_task->ID } = $running_task;
1274 sub handle_task_result {
1275     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1276     my $client_answer = $result->{'answer'};
1277     if( $client_answer =~ s/session_id=(\d+)$// ) {
1278         my $session_id = $1;
1279         if( defined $session_id ) {
1280             my $session_reference = $kernel->ID_id_to_session($session_id);
1281             if( defined $session_reference ) {
1282                 $heap = $session_reference->get_heap();
1283             }
1284         }
1286         if(exists $heap->{'client'}) {
1287             $heap->{'client'}->put($client_answer);
1288         }
1289     }
1290     $kernel->sig(CHLD => "child_reap");
1293 sub handle_task_debug {
1294     my $result = $_[ARG0];
1295     print STDERR "$result\n";
1298 sub handle_task_done {
1299     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1300     delete $heap->{task}->{$task_id};
1303 sub process_task {
1304     no strict "refs";
1305     #CHECK: Not @_[...]?
1306     my ($session, $heap, $task) = @_;
1307     my $error = 0;
1308     my $answer_l;
1309     my ($answer_header, @answer_target_l, $answer_source);
1310     my $client_answer = "";
1312     # prepare all variables needed to process message
1313     #my $msg = $task->{'xmlmessage'};
1314     my $msg = &decode_base64($task->{'xmlmessage'});
1315     my $incoming_id = $task->{'id'};
1316     my $module = $task->{'module'};
1317     my $header =  $task->{'headertag'};
1318     my $session_id = $task->{'sessionid'};
1319     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1320     my $source = @{$msg_hash->{'source'}}[0];
1321     
1322     # set timestamp of incoming client uptodate, so client will not 
1323     # be deleted from known_clients because of expiration
1324     my $act_time = &get_time();
1325     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1326     my $res = $known_clients_db->exec_statement($sql);
1328     ######################
1329     # process incoming msg
1330     if( $error == 0) {
1331         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1332         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1333         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1335         if ( 0 < @{$answer_l} ) {
1336             my $answer_str = join("\n", @{$answer_l});
1337             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1338                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1339             }
1340             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1341         } else {
1342             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1343         }
1345     }
1346     if( !$answer_l ) { $error++ };
1348     ########
1349     # answer
1350     if( $error == 0 ) {
1352         foreach my $answer ( @{$answer_l} ) {
1353             # check outgoing msg to xml validity
1354             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1355             if( not defined $answer_hash ) { next; }
1356             
1357             $answer_header = @{$answer_hash->{'header'}}[0];
1358             @answer_target_l = @{$answer_hash->{'target'}};
1359             $answer_source = @{$answer_hash->{'source'}}[0];
1361             # deliver msg to all targets 
1362             foreach my $answer_target ( @answer_target_l ) {
1364                 # targets of msg are all gosa-si-clients in known_clients_db
1365                 if( $answer_target eq "*" ) {
1366                     # answer is for all clients
1367                     my $sql_statement= "SELECT * FROM known_clients";
1368                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1369                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1370                         my $host_name = $hit->{hostname};
1371                         my $host_key = $hit->{hostkey};
1372                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1373                         &update_jobdb_status_for_send_msgs($answer, $error);
1374                     }
1375                 }
1377                 # targets of msg are all gosa-si-server in known_server_db
1378                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1379                     # answer is for all server in known_server
1380                     my $sql_statement= "SELECT * FROM $known_server_tn";
1381                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1382                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1383                         my $host_name = $hit->{hostname};
1384                         my $host_key = $hit->{hostkey};
1385                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1386                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1387                         &update_jobdb_status_for_send_msgs($answer, $error);
1388                     }
1389                 }
1391                 # target of msg is GOsa
1392                                 elsif( $answer_target eq "GOSA" ) {
1393                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1394                                         my $add_on = "";
1395                     if( defined $session_id ) {
1396                         $add_on = ".session_id=$session_id";
1397                     }
1398                     # answer is for GOSA and has to returned to connected client
1399                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1400                     $client_answer = $gosa_answer.$add_on;
1401                 }
1403                 # target of msg is job queue at this host
1404                 elsif( $answer_target eq "JOBDB") {
1405                     $answer =~ /<header>(\S+)<\/header>/;   
1406                     my $header;
1407                     if( defined $1 ) { $header = $1; }
1408                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1409                     &update_jobdb_status_for_send_msgs($answer, $error);
1410                 }
1412                 # Target of msg is a mac address
1413                 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 ) {
1414                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1415                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1416                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1417                     my $found_ip_flag = 0;
1418                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1419                         my $host_name = $hit->{hostname};
1420                         my $host_key = $hit->{hostkey};
1421                         $answer =~ s/$answer_target/$host_name/g;
1422                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1423                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1424                         &update_jobdb_status_for_send_msgs($answer, $error);
1425                         $found_ip_flag++ ;
1426                     }   
1427                     if ($found_ip_flag == 0) {
1428                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1429                         my $res = $foreign_clients_db->select_dbentry($sql);
1430                         while( my ($hit_num, $hit) = each %{ $res } ) {
1431                             my $host_name = $hit->{hostname};
1432                             my $reg_server = $hit->{regserver};
1433                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1434                             
1435                             # Fetch key for reg_server
1436                             my $reg_server_key;
1437                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1438                             my $res = $known_server_db->select_dbentry($sql);
1439                             if (exists $res->{1}) {
1440                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1441                             } else {
1442                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1443                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1444                                 $reg_server_key = undef;
1445                             }
1447                             # Send answer to server where client is registered
1448                             if (defined $reg_server_key) {
1449                                 $answer =~ s/$answer_target/$host_name/g;
1450                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1451                                 &update_jobdb_status_for_send_msgs($answer, $error);
1452                                 $found_ip_flag++ ;
1453                             }
1454                         }
1455                     }
1456                     if( $found_ip_flag == 0) {
1457                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1458                     }
1460                 # Answer is for one specific host   
1461                 } else {
1462                     # get encrypt_key
1463                     my $encrypt_key = &get_encrypt_key($answer_target);
1464                     if( not defined $encrypt_key ) {
1465                         # unknown target
1466                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1467                         next;
1468                     }
1469                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1470                     &update_jobdb_status_for_send_msgs($answer, $error);
1471                 }
1472             }
1473         }
1474     }
1476     my $filter = POE::Filter::Reference->new();
1477     my %result = ( 
1478             status => "seems ok to me",
1479             answer => $client_answer,
1480             );
1482     my $output = $filter->put( [ \%result ] );
1483     print @$output;
1488 sub session_start {
1489     my ($kernel) = $_[KERNEL];
1490     $global_kernel = $kernel;
1491     $kernel->yield('register_at_foreign_servers');
1492         $kernel->yield('create_fai_server_db', $fai_server_tn );
1493         $kernel->yield('create_fai_release_db', $fai_release_tn );
1494     $kernel->yield('watch_for_next_tasks');
1495         $kernel->sig(USR1 => "sig_handler");
1496         $kernel->sig(USR2 => "recreate_packages_db");
1497         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1498         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1499     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1500         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1501     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1502         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1503     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1505     # Start opsi check
1506     if ($opsi_enabled eq "true") {
1507         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1508     }
1513 sub watch_for_done_jobs {
1514     #CHECK: $heap for what?
1515     my ($kernel,$heap) = @_[KERNEL, HEAP];
1517     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1518         my $res = $job_db->select_dbentry( $sql_statement );
1520     while( my ($id, $hit) = each %{$res} ) {
1521         my $jobdb_id = $hit->{id};
1522         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1523         my $res = $job_db->del_dbentry($sql_statement); 
1524     }
1526     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1530 sub watch_for_opsi_jobs {
1531     my ($kernel) = $_[KERNEL];
1533     # 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 
1534     # opsi install job is to parse the xml message. There is still the correct header.
1535     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1536         my $res = $job_db->select_dbentry( $sql_statement );
1538     # Ask OPSI for an update of the running jobs
1539     while (my ($id, $hit) = each %$res ) {
1540         # Determine current parameters of the job
1541         my $hostId = $hit->{'plainname'};
1542         my $macaddress = $hit->{'macaddress'};
1543         my $progress = $hit->{'progress'};
1545         my $result= {};
1546         
1547         # For hosts, only return the products that are or get installed
1548         my $callobj;
1549         $callobj = {
1550             method  => 'getProductStates_hash',
1551             params  => [ $hostId ],
1552             id  => 1,
1553         };
1554         
1555         my $hres = $opsi_client->call($opsi_url, $callobj);
1556         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1557         if (not &check_opsi_res($hres)) {
1558             my $htmp= $hres->result->{$hostId};
1559         
1560             # Check state != not_installed or action == setup -> load and add
1561             my $products= 0;
1562             my $installed= 0;
1563             my $installing = 0;
1564             my $error= 0;  
1565             my @installed_list;
1566             my @error_list;
1567             my $act_status = "none";
1568             foreach my $product (@{$htmp}){
1570                 if ($product->{'installationStatus'} ne "not_installed" or
1571                         $product->{'actionRequest'} eq "setup"){
1573                     # Increase number of products for this host
1574                     $products++;
1575         
1576                     if ($product->{'installationStatus'} eq "failed"){
1577                         $result->{$product->{'productId'}}= "error";
1578                         unshift(@error_list, $product->{'productId'});
1579                         $error++;
1580                     }
1581                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1582                         $result->{$product->{'productId'}}= "installed";
1583                         unshift(@installed_list, $product->{'productId'});
1584                         $installed++;
1585                     }
1586                     if ($product->{'installationStatus'} eq "installing"){
1587                         $result->{$product->{'productId'}}= "installing";
1588                         $installing++;
1589                         $act_status = "installing - ".$product->{'productId'};
1590                     }
1591                 }
1592             }
1593         
1594             # Estimate "rough" progress, avoid division by zero
1595             if ($products == 0) {
1596                 $result->{'progress'}= 0;
1597             } else {
1598                 $result->{'progress'}= int($installed * 100 / $products);
1599             }
1601             # Set updates in job queue
1602             if ((not $error) && (not $installing) && ($installed)) {
1603                 $act_status = "installed - ".join(", ", @installed_list);
1604             }
1605             if ($error) {
1606                 $act_status = "error - ".join(", ", @error_list);
1607             }
1608             if ($progress ne $result->{'progress'} ) {
1609                 # Updating progress and result 
1610                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1611                 my $update_res = $job_db->update_dbentry($update_statement);
1612             }
1613             if ($progress eq 100) { 
1614                 # Updateing status
1615                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1616                 if ($error) {
1617                     $done_statement .= "status='error'";
1618                 } else {
1619                     $done_statement .= "status='done'";
1620                 }
1621                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1622                 my $done_res = $job_db->update_dbentry($done_statement);
1623             }
1626         }
1627     }
1629     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1633 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1634 sub watch_for_modified_jobs {
1635     my ($kernel,$heap) = @_[KERNEL, HEAP];
1637     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1638     my $res = $job_db->select_dbentry( $sql_statement );
1639     
1640     # if db contains no jobs which should be update, do nothing
1641     if (keys %$res != 0) {
1643         if ($job_synchronization  eq "true") {
1644             # make out of the db result a gosa-si message   
1645             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1646  
1647             # update all other SI-server
1648             &inform_all_other_si_server($update_msg);
1649         }
1651         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1652         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1653         $res = $job_db->update_dbentry($sql_statement);
1654     }
1656     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1660 sub watch_for_new_jobs {
1661         if($watch_for_new_jobs_in_progress == 0) {
1662                 $watch_for_new_jobs_in_progress = 1;
1663                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1665                 # check gosa job quaeue for jobs with executable timestamp
1666                 my $timestamp = &get_time();
1667                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1668                 my $res = $job_db->exec_statement( $sql_statement );
1670                 # Merge all new jobs that would do the same actions
1671                 my @drops;
1672                 my $hits;
1673                 foreach my $hit (reverse @{$res} ) {
1674                         my $macaddress= lc @{$hit}[8];
1675                         my $headertag= @{$hit}[5];
1676                         if(
1677                                 defined($hits->{$macaddress}) &&
1678                                 defined($hits->{$macaddress}->{$headertag}) &&
1679                                 defined($hits->{$macaddress}->{$headertag}[0])
1680                         ) {
1681                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1682                         }
1683                         $hits->{$macaddress}->{$headertag}= $hit;
1684                 }
1686                 # Delete new jobs with a matching job in state 'processing'
1687                 foreach my $macaddress (keys %{$hits}) {
1688                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1689                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1690                                 if(defined($jobdb_id)) {
1691                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1692                                         my $res = $job_db->exec_statement( $sql_statement );
1693                                         foreach my $hit (@{$res}) {
1694                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1695                                         }
1696                                 } else {
1697                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1698                                 }
1699                         }
1700                 }
1702                 # Commit deletion
1703                 $job_db->exec_statementlist(\@drops);
1705                 # Look for new jobs that could be executed
1706                 foreach my $macaddress (keys %{$hits}) {
1708                         # Look if there is an executing job
1709                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1710                         my $res = $job_db->exec_statement( $sql_statement );
1712                         # Skip new jobs for host if there is a processing job
1713                         if(defined($res) and defined @{$res}[0]) {
1714                                 next;
1715                         }
1717                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1718                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1719                                 if(defined($jobdb_id)) {
1720                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1722                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1723                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1724                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1726                                         # expect macaddress is unique!!!!!!
1727                                         my $target = $res_hash->{1}->{hostname};
1729                                         # change header
1730                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1732                                         # add sqlite_id
1733                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1735                                         $job_msg =~ /<header>(\S+)<\/header>/;
1736                                         my $header = $1 ;
1737                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1739                                         # update status in job queue to 'processing'
1740                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1741                                         my $res = $job_db->update_dbentry($sql_statement);
1742 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1744                                         # We don't want parallel processing
1745                                         last;
1746                                 }
1747                         }
1748                 }
1750                 $watch_for_new_jobs_in_progress = 0;
1751                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1752         }
1756 sub watch_for_new_messages {
1757     my ($kernel,$heap) = @_[KERNEL, HEAP];
1758     my @coll_user_msg;   # collection list of outgoing messages
1759     
1760     # check messaging_db for new incoming messages with executable timestamp
1761     my $timestamp = &get_time();
1762     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1763     my $res = $messaging_db->exec_statement( $sql_statement );
1764         foreach my $hit (@{$res}) {
1766         # create outgoing messages
1767         my $message_to = @{$hit}[3];
1768         # translate message_to to plain login name
1769         my @message_to_l = split(/,/, $message_to);  
1770                 my %receiver_h; 
1771                 foreach my $receiver (@message_to_l) {
1772                         if ($receiver =~ /^u_([\s\S]*)$/) {
1773                                 $receiver_h{$1} = 0;
1774                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1775                                 my $group_name = $1;
1776                                 # fetch all group members from ldap and add them to receiver hash
1777                                 my $ldap_handle = &get_ldap_handle();
1778                                 if (defined $ldap_handle) {
1779                                                 my $mesg = $ldap_handle->search(
1780                                                                                 base => $ldap_base,
1781                                                                                 scope => 'sub',
1782                                                                                 attrs => ['memberUid'],
1783                                                                                 filter => "cn=$group_name",
1784                                                                                 );
1785                                                 if ($mesg->count) {
1786                                                                 my @entries = $mesg->entries;
1787                                                                 foreach my $entry (@entries) {
1788                                                                                 my @receivers= $entry->get_value("memberUid");
1789                                                                                 foreach my $receiver (@receivers) { 
1790                                                                                                 $receiver_h{$1} = 0;
1791                                                                                 }
1792                                                                 }
1793                                                 } 
1794                                                 # translating errors ?
1795                                                 if ($mesg->code) {
1796                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1797                                                 }
1798                                 # ldap handle error ?           
1799                                 } else {
1800                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1801                                 }
1802                         } else {
1803                                 my $sbjct = &encode_base64(@{$hit}[1]);
1804                                 my $msg = &encode_base64(@{$hit}[7]);
1805                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1806                         }
1807                 }
1808                 my @receiver_l = keys(%receiver_h);
1810         my $message_id = @{$hit}[0];
1812         #add each outgoing msg to messaging_db
1813         my $receiver;
1814         foreach $receiver (@receiver_l) {
1815             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1816                 "VALUES ('".
1817                 $message_id."', '".    # id
1818                 @{$hit}[1]."', '".     # subject
1819                 @{$hit}[2]."', '".     # message_from
1820                 $receiver."', '".      # message_to
1821                 "none"."', '".         # flag
1822                 "out"."', '".          # direction
1823                 @{$hit}[6]."', '".     # delivery_time
1824                 @{$hit}[7]."', '".     # message
1825                 $timestamp."'".     # timestamp
1826                 ")";
1827             &daemon_log("M DEBUG: $sql_statement", 1);
1828             my $res = $messaging_db->exec_statement($sql_statement);
1829             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1830         }
1832         # set incoming message to flag d=deliverd
1833         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1834         &daemon_log("M DEBUG: $sql_statement", 7);
1835         $res = $messaging_db->update_dbentry($sql_statement);
1836         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1837     }
1839     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1840     return;
1843 sub watch_for_delivery_messages {
1844     my ($kernel, $heap) = @_[KERNEL, HEAP];
1846     # select outgoing messages
1847     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1848     #&daemon_log("0 DEBUG: $sql", 7);
1849     my $res = $messaging_db->exec_statement( $sql_statement );
1850     
1851     # build out msg for each    usr
1852     foreach my $hit (@{$res}) {
1853         my $receiver = @{$hit}[3];
1854         my $msg_id = @{$hit}[0];
1855         my $subject = @{$hit}[1];
1856         my $message = @{$hit}[7];
1858         # resolve usr -> host where usr is logged in
1859         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1860         #&daemon_log("0 DEBUG: $sql", 7);
1861         my $res = $login_users_db->exec_statement($sql);
1863         # reciver is logged in nowhere
1864         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1866                 my $send_succeed = 0;
1867                 foreach my $hit (@$res) {
1868                                 my $receiver_host = @$hit[0];
1869                 my $delivered2host = 0;
1870                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1872                                 # Looking for host in know_clients_db 
1873                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1874                                 my $res = $known_clients_db->exec_statement($sql);
1876                 # Host is known in known_clients_db
1877                 if (ref(@$res[0]) eq "ARRAY") {
1878                     my $receiver_key = @{@{$res}[0]}[2];
1879                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1880                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1881                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1882                     if ($error == 0 ) {
1883                         $send_succeed++ ;
1884                         $delivered2host++ ;
1885                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1886                     } else {
1887                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1888                     }
1889                 }
1890                 
1891                 # Message already send, do not need to do anything more, otherwise ...
1892                 if ($delivered2host) { next;}
1893     
1894                 # ...looking for host in foreign_clients_db
1895                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1896                 $res = $foreign_clients_db->exec_statement($sql);
1897   
1898                                 # Host is known in foreign_clients_db 
1899                                 if (ref(@$res[0]) eq "ARRAY") { 
1900                     my $registration_server = @{@{$res}[0]}[2];
1901                     
1902                     # Fetch encryption key for registration server
1903                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1904                     my $res = $known_server_db->exec_statement($sql);
1905                     if (ref(@$res[0]) eq "ARRAY") { 
1906                         my $registration_server_key = @{@{$res}[0]}[3];
1907                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1908                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1909                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1910                         if ($error == 0 ) {
1911                             $send_succeed++ ;
1912                             $delivered2host++ ;
1913                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1914                         } else {
1915                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1916                         }
1918                     } else {
1919                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1920                                 "registrated at server '$registration_server', ".
1921                                 "but no data available in known_server_db ", 1); 
1922                     }
1923                 }
1924                 
1925                 if (not $delivered2host) {
1926                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1927                 }
1928                 }
1930                 if ($send_succeed) {
1931                                 # set outgoing msg at db to deliverd
1932                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1933                                 my $res = $messaging_db->exec_statement($sql); 
1934                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1935                 } else {
1936             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1937         }
1938         }
1940     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1941     return;
1945 sub watch_for_done_messages {
1946     my ($kernel,$heap) = @_[KERNEL, HEAP];
1948     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1949     #&daemon_log("0 DEBUG: $sql", 7);
1950     my $res = $messaging_db->exec_statement($sql); 
1952     foreach my $hit (@{$res}) {
1953         my $msg_id = @{$hit}[0];
1955         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1956         #&daemon_log("0 DEBUG: $sql", 7); 
1957         my $res = $messaging_db->exec_statement($sql);
1959         # not all usr msgs have been seen till now
1960         if ( ref(@$res[0]) eq "ARRAY") { next; }
1961         
1962         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1963         #&daemon_log("0 DEBUG: $sql", 7);
1964         $res = $messaging_db->exec_statement($sql);
1965     
1966     }
1968     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1969     return;
1973 sub watch_for_old_known_clients {
1974     my ($kernel,$heap) = @_[KERNEL, HEAP];
1976     my $sql_statement = "SELECT * FROM $known_clients_tn";
1977     my $res = $known_clients_db->select_dbentry( $sql_statement );
1979     my $act_time = int(&get_time());
1981     while ( my ($hit_num, $hit) = each %$res) {
1982         my $expired_timestamp = int($hit->{'timestamp'});
1983         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1984         my $dt = DateTime->new( year   => $1,
1985                 month  => $2,
1986                 day    => $3,
1987                 hour   => $4,
1988                 minute => $5,
1989                 second => $6,
1990                 );
1992         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1993         $expired_timestamp = $dt->ymd('').$dt->hms('');
1994         if ($act_time > $expired_timestamp) {
1995             my $hostname = $hit->{'hostname'};
1996             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1997             my $del_res = $known_clients_db->exec_statement($del_sql);
1999             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2000         }
2002     }
2004     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2008 sub watch_for_next_tasks {
2009     my ($kernel,$heap) = @_[KERNEL, HEAP];
2011     my $sql = "SELECT * FROM $incoming_tn";
2012     my $res = $incoming_db->select_dbentry($sql);
2014     while ( my ($hit_num, $hit) = each %$res) {
2015         my $headertag = $hit->{'headertag'};
2016         if ($headertag =~ /^answer_(\d+)/) {
2017             # do not start processing, this message is for a still running POE::Wheel
2018             next;
2019         }
2020         my $message_id = $hit->{'id'};
2021         $kernel->yield('next_task', $hit);
2023         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2024         my $res = $incoming_db->exec_statement($sql);
2025     }
2027     $kernel->delay_set('watch_for_next_tasks', 0.1); 
2031 sub get_ldap_handle {
2032         my ($session_id) = @_;
2033         my $heap;
2034         my $ldap_handle;
2036         if (not defined $session_id ) { $session_id = 0 };
2037         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2039         if ($session_id == 0) {
2040                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2041                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2042                 $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!"); 
2044         } else {
2045                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2046                 if( defined $session_reference ) {
2047                         $heap = $session_reference->get_heap();
2048                 }
2050                 if (not defined $heap) {
2051                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2052                         return;
2053                 }
2055                 # TODO: This "if" is nonsense, because it doesn't prove that the
2056                 #       used handle is still valid - or if we've to reconnect...
2057                 #if (not exists $heap->{ldap_handle}) {
2058                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2059                         $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!"); 
2060                         $heap->{ldap_handle} = $ldap_handle;
2061                 #}
2062         }
2063         return $ldap_handle;
2067 sub change_fai_state {
2068     my ($st, $targets, $session_id) = @_;
2069     $session_id = 0 if not defined $session_id;
2070     # Set FAI state to localboot
2071     my %mapActions= (
2072         reboot    => '',
2073         update    => 'softupdate',
2074         localboot => 'localboot',
2075         reinstall => 'install',
2076         rescan    => '',
2077         wake      => '',
2078         memcheck  => 'memcheck',
2079         sysinfo   => 'sysinfo',
2080         install   => 'install',
2081     );
2083     # Return if this is unknown
2084     if (!exists $mapActions{ $st }){
2085         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2086       return;
2087     }
2089     my $state= $mapActions{ $st };
2091     my $ldap_handle = &get_ldap_handle($session_id);
2092     if( defined($ldap_handle) ) {
2094       # Build search filter for hosts
2095         my $search= "(&(objectClass=GOhard)";
2096         foreach (@{$targets}){
2097             $search.= "(macAddress=$_)";
2098         }
2099         $search.= ")";
2101       # If there's any host inside of the search string, procress them
2102         if (!($search =~ /macAddress/)){
2103             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2104             return;
2105         }
2107       # Perform search for Unit Tag
2108       my $mesg = $ldap_handle->search(
2109           base   => $ldap_base,
2110           scope  => 'sub',
2111           attrs  => ['dn', 'FAIstate', 'objectClass'],
2112           filter => "$search"
2113           );
2115           if ($mesg->count) {
2116                   my @entries = $mesg->entries;
2117                   if (0 == @entries) {
2118                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2119                   }
2121                   foreach my $entry (@entries) {
2122                           # Only modify entry if it is not set to '$state'
2123                           if ($entry->get_value("FAIstate") ne "$state"){
2124                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2125                                   my $result;
2126                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2127                                   if (exists $tmp{'FAIobject'}){
2128                                           if ($state eq ''){
2129                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2130                                                           delete => [ FAIstate => [] ] ]);
2131                                           } else {
2132                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2133                                                           replace => [ FAIstate => $state ] ]);
2134                                           }
2135                                   } elsif ($state ne ''){
2136                                           $result= $ldap_handle->modify($entry->dn, changes => [
2137                                                   add     => [ objectClass => 'FAIobject' ],
2138                                                   add     => [ FAIstate => $state ] ]);
2139                                   }
2141                                   # Errors?
2142                                   if ($result->code){
2143                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2144                                   }
2145                           } else {
2146                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2147                           }  
2148                   }
2149           } else {
2150                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2151           }
2153     # if no ldap handle defined
2154     } else {
2155         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2156     }
2158         return;
2162 sub change_goto_state {
2163     my ($st, $targets, $session_id) = @_;
2164     $session_id = 0  if not defined $session_id;
2166     # Switch on or off?
2167     my $state= $st eq 'active' ? 'active': 'locked';
2169     my $ldap_handle = &get_ldap_handle($session_id);
2170     if( defined($ldap_handle) ) {
2172       # Build search filter for hosts
2173       my $search= "(&(objectClass=GOhard)";
2174       foreach (@{$targets}){
2175         $search.= "(macAddress=$_)";
2176       }
2177       $search.= ")";
2179       # If there's any host inside of the search string, procress them
2180       if (!($search =~ /macAddress/)){
2181         return;
2182       }
2184       # Perform search for Unit Tag
2185       my $mesg = $ldap_handle->search(
2186           base   => $ldap_base,
2187           scope  => 'sub',
2188           attrs  => ['dn', 'gotoMode'],
2189           filter => "$search"
2190           );
2192       if ($mesg->count) {
2193         my @entries = $mesg->entries;
2194         foreach my $entry (@entries) {
2196           # Only modify entry if it is not set to '$state'
2197           if ($entry->get_value("gotoMode") ne $state){
2199             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2200             my $result;
2201             $result= $ldap_handle->modify($entry->dn, changes => [
2202                                                 replace => [ gotoMode => $state ] ]);
2204             # Errors?
2205             if ($result->code){
2206               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2207             }
2209           }
2210         }
2211       } else {
2212                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2213           }
2215     }
2219 sub run_recreate_packages_db {
2220     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2221     my $session_id = $session->ID;
2222         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2223         $kernel->yield('create_fai_release_db', $fai_release_tn);
2224         $kernel->yield('create_fai_server_db', $fai_server_tn);
2225         return;
2229 sub run_create_fai_server_db {
2230     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2231     my $session_id = $session->ID;
2232     my $task = POE::Wheel::Run->new(
2233             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2234             StdoutEvent  => "session_run_result",
2235             StderrEvent  => "session_run_debug",
2236             CloseEvent   => "session_run_done",
2237             );
2239     $heap->{task}->{ $task->ID } = $task;
2240     return;
2244 sub create_fai_server_db {
2245     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2246         my $result;
2248         if (not defined $session_id) { $session_id = 0; }
2249     my $ldap_handle = &get_ldap_handle();
2250         if(defined($ldap_handle)) {
2251                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2252                 my $mesg= $ldap_handle->search(
2253                         base   => $ldap_base,
2254                         scope  => 'sub',
2255                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2256                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2257                 );
2258                 if($mesg->{'resultCode'} == 0 &&
2259                    $mesg->count != 0) {
2260                    foreach my $entry (@{$mesg->{entries}}) {
2261                            if($entry->exists('FAIrepository')) {
2262                                    # Add an entry for each Repository configured for server
2263                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2264                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2265                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2266                                                    $result= $fai_server_db->add_dbentry( { 
2267                                                                    table => $table_name,
2268                                                                    primkey => ['server', 'release', 'tag'],
2269                                                                    server => $tmp_url,
2270                                                                    release => $tmp_release,
2271                                                                    sections => $tmp_sections,
2272                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2273                                                            } );
2274                                            }
2275                                    }
2276                            }
2277                    }
2278                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2280                 # TODO: Find a way to post the 'create_packages_list_db' event
2281                 if(not defined($dont_create_packages_list)) {
2282                         &create_packages_list_db(undef, undef, $session_id);
2283                 }
2284         }       
2285     
2286     $ldap_handle->disconnect;
2287         return $result;
2291 sub run_create_fai_release_db {
2292     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2293         my $session_id = $session->ID;
2294     my $task = POE::Wheel::Run->new(
2295             Program => sub { &create_fai_release_db($table_name, $session_id) },
2296             StdoutEvent  => "session_run_result",
2297             StderrEvent  => "session_run_debug",
2298             CloseEvent   => "session_run_done",
2299             );
2301     $heap->{task}->{ $task->ID } = $task;
2302     return;
2306 sub create_fai_release_db {
2307         my ($table_name, $session_id) = @_;
2308         my $result;
2310     # used for logging
2311     if (not defined $session_id) { $session_id = 0; }
2313     my $ldap_handle = &get_ldap_handle();
2314         if(defined($ldap_handle)) {
2315                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2316                 my $mesg= $ldap_handle->search(
2317                         base   => $ldap_base,
2318                         scope  => 'sub',
2319                         attrs  => [],
2320                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2321                 );
2322                 if($mesg->{'resultCode'} == 0 &&
2323                         $mesg->count != 0) {
2324                         # Walk through all possible FAI container ou's
2325                         my @sql_list;
2326                         my $timestamp= &get_time();
2327                         foreach my $ou (@{$mesg->{entries}}) {
2328                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2329                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2330                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2331                                         if(@tmp_array) {
2332                                                 foreach my $entry (@tmp_array) {
2333                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2334                                                                 my $sql= 
2335                                                                 "INSERT INTO $table_name "
2336                                                                 ."(timestamp, release, class, type, state) VALUES ("
2337                                                                 .$timestamp.","
2338                                                                 ."'".$entry->{'release'}."',"
2339                                                                 ."'".$entry->{'class'}."',"
2340                                                                 ."'".$entry->{'type'}."',"
2341                                                                 ."'".$entry->{'state'}."')";
2342                                                                 push @sql_list, $sql;
2343                                                         }
2344                                                 }
2345                                         }
2346                                 }
2347                         }
2349                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2350                         if(@sql_list) {
2351                                 unshift @sql_list, "VACUUM";
2352                                 unshift @sql_list, "DELETE FROM $table_name";
2353                                 $fai_release_db->exec_statementlist(\@sql_list);
2354                         }
2355                         daemon_log("$session_id DEBUG: Done with inserting",7);
2356                 }
2357                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2358         }
2359     $ldap_handle->disconnect;
2360         return $result;
2363 sub get_fai_types {
2364         my $tmp_classes = shift || return undef;
2365         my @result;
2367         foreach my $type(keys %{$tmp_classes}) {
2368                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2369                         my $entry = {
2370                                 type => $type,
2371                                 state => $tmp_classes->{$type}[0],
2372                         };
2373                         push @result, $entry;
2374                 }
2375         }
2377         return @result;
2380 sub get_fai_state {
2381         my $result = "";
2382         my $tmp_classes = shift || return $result;
2384         foreach my $type(keys %{$tmp_classes}) {
2385                 if(defined($tmp_classes->{$type}[0])) {
2386                         $result = $tmp_classes->{$type}[0];
2387                         
2388                 # State is equal for all types in class
2389                         last;
2390                 }
2391         }
2393         return $result;
2396 sub resolve_fai_classes {
2397         my ($fai_base, $ldap_handle, $session_id) = @_;
2398         if (not defined $session_id) { $session_id = 0; }
2399         my $result;
2400         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2401         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2402         my $fai_classes;
2404         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2405         my $mesg= $ldap_handle->search(
2406                 base   => $fai_base,
2407                 scope  => 'sub',
2408                 attrs  => ['cn','objectClass','FAIstate'],
2409                 filter => $fai_filter,
2410         );
2411         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2413         if($mesg->{'resultCode'} == 0 &&
2414                 $mesg->count != 0) {
2415                 foreach my $entry (@{$mesg->{entries}}) {
2416                         if($entry->exists('cn')) {
2417                                 my $tmp_dn= $entry->dn();
2419                                 # Skip classname and ou dn parts for class
2420                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2422                                 # Skip classes without releases
2423                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2424                                         next;
2425                                 }
2427                                 my $tmp_cn= $entry->get_value('cn');
2428                                 my $tmp_state= $entry->get_value('FAIstate');
2430                                 my $tmp_type;
2431                                 # Get FAI type
2432                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2433                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2434                                                 $tmp_type= $oclass;
2435                                                 last;
2436                                         }
2437                                 }
2439                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2440                                         # A Subrelease
2441                                         my @sub_releases = split(/,/, $tmp_release);
2443                                         # Walk through subreleases and build hash tree
2444                                         my $hash;
2445                                         while(my $tmp_sub_release = pop @sub_releases) {
2446                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2447                                         }
2448                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2449                                 } else {
2450                                         # A branch, no subrelease
2451                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2452                                 }
2453                         } elsif (!$entry->exists('cn')) {
2454                                 my $tmp_dn= $entry->dn();
2455                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2457                                 # Skip classes without releases
2458                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2459                                         next;
2460                                 }
2462                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2463                                         # A Subrelease
2464                                         my @sub_releases= split(/,/, $tmp_release);
2466                                         # Walk through subreleases and build hash tree
2467                                         my $hash;
2468                                         while(my $tmp_sub_release = pop @sub_releases) {
2469                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2470                                         }
2471                                         # Remove the last two characters
2472                                         chop($hash);
2473                                         chop($hash);
2475                                         eval('$fai_classes->'.$hash.'= {}');
2476                                 } else {
2477                                         # A branch, no subrelease
2478                                         if(!exists($fai_classes->{$tmp_release})) {
2479                                                 $fai_classes->{$tmp_release} = {};
2480                                         }
2481                                 }
2482                         }
2483                 }
2485                 # The hash is complete, now we can honor the copy-on-write based missing entries
2486                 foreach my $release (keys %$fai_classes) {
2487                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2488                 }
2489         }
2490         return $result;
2493 sub apply_fai_inheritance {
2494        my $fai_classes = shift || return {};
2495        my $tmp_classes;
2497        # Get the classes from the branch
2498        foreach my $class (keys %{$fai_classes}) {
2499                # Skip subreleases
2500                if($class =~ /^ou=.*$/) {
2501                        next;
2502                } else {
2503                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2504                }
2505        }
2507        # Apply to each subrelease
2508        foreach my $subrelease (keys %{$fai_classes}) {
2509                if($subrelease =~ /ou=/) {
2510                        foreach my $tmp_class (keys %{$tmp_classes}) {
2511                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2512                                        $fai_classes->{$subrelease}->{$tmp_class} =
2513                                        deep_copy($tmp_classes->{$tmp_class});
2514                                } else {
2515                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2516                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2517                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2518                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2519                                                }
2520                                        }
2521                                }
2522                        }
2523                }
2524        }
2526        # Find subreleases in deeper levels
2527        foreach my $subrelease (keys %{$fai_classes}) {
2528                if($subrelease =~ /ou=/) {
2529                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2530                                if($subsubrelease =~ /ou=/) {
2531                                        apply_fai_inheritance($fai_classes->{$subrelease});
2532                                }
2533                        }
2534                }
2535        }
2537        return $fai_classes;
2540 sub get_fai_release_entries {
2541         my $tmp_classes = shift || return;
2542         my $parent = shift || "";
2543         my @result = shift || ();
2545         foreach my $entry (keys %{$tmp_classes}) {
2546                 if(defined($entry)) {
2547                         if($entry =~ /^ou=.*$/) {
2548                                 my $release_name = $entry;
2549                                 $release_name =~ s/ou=//g;
2550                                 if(length($parent)>0) {
2551                                         $release_name = $parent."/".$release_name;
2552                                 }
2553                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2554                                 foreach my $bufentry(@bufentries) {
2555                                         push @result, $bufentry;
2556                                 }
2557                         } else {
2558                                 my @types = get_fai_types($tmp_classes->{$entry});
2559                                 foreach my $type (@types) {
2560                                         push @result, 
2561                                         {
2562                                                 'class' => $entry,
2563                                                 'type' => $type->{'type'},
2564                                                 'release' => $parent,
2565                                                 'state' => $type->{'state'},
2566                                         };
2567                                 }
2568                         }
2569                 }
2570         }
2572         return @result;
2575 sub deep_copy {
2576         my $this = shift;
2577         if (not ref $this) {
2578                 $this;
2579         } elsif (ref $this eq "ARRAY") {
2580                 [map deep_copy($_), @$this];
2581         } elsif (ref $this eq "HASH") {
2582                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2583         } else { die "what type is $_?" }
2587 sub session_run_result {
2588     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2589     $kernel->sig(CHLD => "child_reap");
2592 sub session_run_debug {
2593     my $result = $_[ARG0];
2594     print STDERR "$result\n";
2597 sub session_run_done {
2598     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2599     delete $heap->{task}->{$task_id};
2603 sub create_sources_list {
2604         my $session_id = shift;
2605         my $ldap_handle = &main::get_ldap_handle;
2606         my $result="/tmp/gosa_si_tmp_sources_list";
2608         # Remove old file
2609         if(stat($result)) {
2610                 unlink($result);
2611                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2612         }
2614         my $fh;
2615         open($fh, ">$result");
2616         if (not defined $fh) {
2617                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2618                 return undef;
2619         }
2620         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2621                 my $mesg=$ldap_handle->search(
2622                         base    => $main::ldap_server_dn,
2623                         scope   => 'base',
2624                         attrs   => 'FAIrepository',
2625                         filter  => 'objectClass=FAIrepositoryServer'
2626                 );
2627                 if($mesg->count) {
2628                         foreach my $entry(@{$mesg->{'entries'}}) {
2629                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2630                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2631                                         my $line = "deb $server $release";
2632                                         $sections =~ s/,/ /g;
2633                                         $line.= " $sections";
2634                                         print $fh $line."\n";
2635                                 }
2636                         }
2637                 }
2638         } else {
2639                 if (defined $main::ldap_server_dn){
2640                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2641                 } else {
2642                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2643                 }
2644         }
2645         close($fh);
2647         return $result;
2651 sub run_create_packages_list_db {
2652     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2653         my $session_id = $session->ID;
2655         my $task = POE::Wheel::Run->new(
2656                                         Priority => +20,
2657                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2658                                         StdoutEvent  => "session_run_result",
2659                                         StderrEvent  => "session_run_debug",
2660                                         CloseEvent   => "session_run_done",
2661                                         );
2662         $heap->{task}->{ $task->ID } = $task;
2666 sub create_packages_list_db {
2667         my ($ldap_handle, $sources_file, $session_id) = @_;
2668         
2669         # it should not be possible to trigger a recreation of packages_list_db
2670         # while packages_list_db is under construction, so set flag packages_list_under_construction
2671         # which is tested befor recreation can be started
2672         if (-r $packages_list_under_construction) {
2673                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2674                 return;
2675         } else {
2676                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2677                 # set packages_list_under_construction to true
2678                 system("touch $packages_list_under_construction");
2679                 @packages_list_statements=();
2680         }
2682         if (not defined $session_id) { $session_id = 0; }
2683         if (not defined $ldap_handle) { 
2684                 $ldap_handle= &get_ldap_handle();
2686                 if (not defined $ldap_handle) {
2687                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2688                         unlink($packages_list_under_construction);
2689                         return;
2690                 }
2691         }
2692         if (not defined $sources_file) { 
2693                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2694                 $sources_file = &create_sources_list($session_id);
2695         }
2697         if (not defined $sources_file) {
2698                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2699                 unlink($packages_list_under_construction);
2700                 return;
2701         }
2703         my $line;
2705         open(CONFIG, "<$sources_file") or do {
2706                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2707                 unlink($packages_list_under_construction);
2708                 return;
2709         };
2711         # Read lines
2712         while ($line = <CONFIG>){
2713                 # Unify
2714                 chop($line);
2715                 $line =~ s/^\s+//;
2716                 $line =~ s/^\s+/ /;
2718                 # Strip comments
2719                 $line =~ s/#.*$//g;
2721                 # Skip empty lines
2722                 if ($line =~ /^\s*$/){
2723                         next;
2724                 }
2726                 # Interpret deb line
2727                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2728                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2729                         my $section;
2730                         foreach $section (split(' ', $sections)){
2731                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2732                         }
2733                 }
2734         }
2736         close (CONFIG);
2739         find(\&cleanup_and_extract, keys( %repo_dirs ));
2740         &main::strip_packages_list_statements();
2741         unshift @packages_list_statements, "VACUUM";
2742         $packages_list_db->exec_statementlist(\@packages_list_statements);
2743         unlink($packages_list_under_construction);
2744         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2745         return;
2748 # This function should do some intensive task to minimize the db-traffic
2749 sub strip_packages_list_statements {
2750     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2751         my @new_statement_list=();
2752         my $hash;
2753         my $insert_hash;
2754         my $update_hash;
2755         my $delete_hash;
2756         my $local_timestamp=get_time();
2758         foreach my $existing_entry (@existing_entries) {
2759                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2760         }
2762         foreach my $statement (@packages_list_statements) {
2763                 if($statement =~ /^INSERT/i) {
2764                         # Assign the values from the insert statement
2765                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2766                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2767                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2768                                 # If section or description has changed, update the DB
2769                                 if( 
2770                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2771                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2772                                 ) {
2773                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2774                                 }
2775                         } else {
2776                                 # Insert a non-existing entry to db
2777                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2778                         }
2779                 } elsif ($statement =~ /^UPDATE/i) {
2780                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2781                         /^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;
2782                         foreach my $distribution (keys %{$hash}) {
2783                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2784                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2785                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2786                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2787                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2788                                                 my $section;
2789                                                 my $description;
2790                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2791                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2792                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2793                                                 }
2794                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2795                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2796                                                 }
2797                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2798                                         }
2799                                 }
2800                         }
2801                 }
2802         }
2804         # TODO: Check for orphaned entries
2806         # unroll the insert_hash
2807         foreach my $distribution (keys %{$insert_hash}) {
2808                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2809                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2810                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2811                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2812                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2813                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2814                                 ."'$local_timestamp')";
2815                         }
2816                 }
2817         }
2819         # unroll the update hash
2820         foreach my $distribution (keys %{$update_hash}) {
2821                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2822                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2823                                 my $set = "";
2824                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2825                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2826                                 }
2827                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2828                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2829                                 }
2830                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2831                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2832                                 }
2833                                 if(defined($set) and length($set) > 0) {
2834                                         $set .= "timestamp = '$local_timestamp'";
2835                                 } else {
2836                                         next;
2837                                 }
2838                                 push @new_statement_list, 
2839                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2840                                         ." distribution = '$distribution'"
2841                                         ." AND package = '$package'"
2842                                         ." AND version = '$version'";
2843                         }
2844                 }
2845         }
2847         @packages_list_statements = @new_statement_list;
2851 sub parse_package_info {
2852     my ($baseurl, $dist, $section, $session_id)= @_;
2853     my ($package);
2854     if (not defined $session_id) { $session_id = 0; }
2855     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2856     $repo_dirs{ "${repo_path}/pool" } = 1;
2858     foreach $package ("Packages.gz"){
2859         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2860         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2861         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2862     }
2863     
2867 sub get_package {
2868     my ($url, $dest, $session_id)= @_;
2869     if (not defined $session_id) { $session_id = 0; }
2871     my $tpath = dirname($dest);
2872     -d "$tpath" || mkpath "$tpath";
2874     # This is ugly, but I've no time to take a look at "how it works in perl"
2875     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2876         system("gunzip -cd '$dest' > '$dest.in'");
2877         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2878         unlink($dest);
2879         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2880     } else {
2881         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2882     }
2883     return 0;
2887 sub parse_package {
2888     my ($path, $dist, $srv_path, $session_id)= @_;
2889     if (not defined $session_id) { $session_id = 0;}
2890     my ($package, $version, $section, $description);
2891     my $PACKAGES;
2892     my $timestamp = &get_time();
2894     if(not stat("$path.in")) {
2895         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2896         return;
2897     }
2899     open($PACKAGES, "<$path.in");
2900     if(not defined($PACKAGES)) {
2901         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2902         return;
2903     }
2905     # Read lines
2906     while (<$PACKAGES>){
2907         my $line = $_;
2908         # Unify
2909         chop($line);
2911         # Use empty lines as a trigger
2912         if ($line =~ /^\s*$/){
2913             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2914             push(@packages_list_statements, $sql);
2915             $package = "none";
2916             $version = "none";
2917             $section = "none";
2918             $description = "none"; 
2919             next;
2920         }
2922         # Trigger for package name
2923         if ($line =~ /^Package:\s/){
2924             ($package)= ($line =~ /^Package: (.*)$/);
2925             next;
2926         }
2928         # Trigger for version
2929         if ($line =~ /^Version:\s/){
2930             ($version)= ($line =~ /^Version: (.*)$/);
2931             next;
2932         }
2934         # Trigger for description
2935         if ($line =~ /^Description:\s/){
2936             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2937             next;
2938         }
2940         # Trigger for section
2941         if ($line =~ /^Section:\s/){
2942             ($section)= ($line =~ /^Section: (.*)$/);
2943             next;
2944         }
2946         # Trigger for filename
2947         if ($line =~ /^Filename:\s/){
2948             my ($filename) = ($line =~ /^Filename: (.*)$/);
2949             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2950             next;
2951         }
2952     }
2954     close( $PACKAGES );
2955     unlink( "$path.in" );
2959 sub store_fileinfo {
2960     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2962     my %fileinfo = (
2963         'package' => $package,
2964         'dist' => $dist,
2965         'version' => $vers,
2966     );
2968     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2972 sub cleanup_and_extract {
2973     my $fileinfo = $repo_files{ $File::Find::name };
2975     if( defined $fileinfo ) {
2977         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2978         my $sql;
2979         my $package = $fileinfo->{ 'package' };
2980         my $newver = $fileinfo->{ 'version' };
2982         mkpath($dir);
2983         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2985                 if( -f "$dir/DEBIAN/templates" ) {
2987                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
2989                         my $tmpl= "";
2990                         {
2991                                 local $/=undef;
2992                                 open FILE, "$dir/DEBIAN/templates";
2993                                 $tmpl = &encode_base64(<FILE>);
2994                                 close FILE;
2995                         }
2996                         rmtree("$dir/DEBIAN/templates");
2998                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2999                 push @packages_list_statements, $sql;
3000                 }
3001     }
3003     return;
3007 sub register_at_foreign_servers {   
3008     my ($kernel) = $_[KERNEL];
3010     # hole alle bekannten server aus known_server_db
3011     my $server_sql = "SELECT * FROM $known_server_tn";
3012     my $server_res = $known_server_db->exec_statement($server_sql);
3014     # no entries in known_server_db
3015     if (not ref(@$server_res[0]) eq "ARRAY") { 
3016         # TODO
3017     }
3019     # detect already connected clients
3020     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3021     my $client_res = $known_clients_db->exec_statement($client_sql);
3023     # send my server details to all other gosa-si-server within the network
3024     foreach my $hit (@$server_res) {
3025         my $hostname = @$hit[0];
3026         my $hostkey = &create_passwd;
3028         # add already connected clients to registration message 
3029         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3030         &add_content2xml_hash($myhash, 'key', $hostkey);
3031         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3033         # add locally loaded gosa-si modules to registration message
3034         my $loaded_modules = {};
3035         while (my ($package, $pck_info) = each %$known_modules) {
3036             foreach my $act_module (keys(%{@$pck_info[2]})) {
3037                 $loaded_modules->{$act_module} = ""; 
3038             }
3039         }
3040         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3042         # add macaddress to registration message
3043         my ($host_ip, $host_port) = split(/:/, $hostname);
3044         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3045         my $network_interface= &get_interface_for_ip($local_ip);
3046         my $host_mac = &get_mac_for_interface($network_interface);
3047         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3048         
3049         # build registration message and send it
3050         my $foreign_server_msg = &create_xml_string($myhash);
3051         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3052     }
3053     
3054     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3055     return;
3059 #==== MAIN = main ==============================================================
3060 #  parse commandline options
3061 Getopt::Long::Configure( "bundling" );
3062 GetOptions("h|help" => \&usage,
3063         "c|config=s" => \$cfg_file,
3064         "f|foreground" => \$foreground,
3065         "v|verbose+" => \$verbose,
3066         "no-arp+" => \$no_arp,
3067            );
3069 #  read and set config parameters
3070 &check_cmdline_param ;
3071 &read_configfile($cfg_file, %cfg_defaults);
3072 &check_pid;
3074 $SIG{CHLD} = 'IGNORE';
3076 # forward error messages to logfile
3077 if( ! $foreground ) {
3078   open( STDIN,  '+>/dev/null' );
3079   open( STDOUT, '+>&STDIN'    );
3080   open( STDERR, '+>&STDIN'    );
3083 # Just fork, if we are not in foreground mode
3084 if( ! $foreground ) { 
3085     chdir '/'                 or die "Can't chdir to /: $!";
3086     $pid = fork;
3087     setsid                    or die "Can't start a new session: $!";
3088     umask 0;
3089 } else { 
3090     $pid = $$; 
3093 # Do something useful - put our PID into the pid_file
3094 if( 0 != $pid ) {
3095     open( LOCK_FILE, ">$pid_file" );
3096     print LOCK_FILE "$pid\n";
3097     close( LOCK_FILE );
3098     if( !$foreground ) { 
3099         exit( 0 ) 
3100     };
3103 # parse head url and revision from svn
3104 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3105 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3106 $server_headURL = defined $1 ? $1 : 'unknown' ;
3107 $server_revision = defined $2 ? $2 : 'unknown' ;
3108 if ($server_headURL =~ /\/tag\// || 
3109         $server_headURL =~ /\/branches\// ) {
3110     $server_status = "stable"; 
3111 } else {
3112     $server_status = "developmental" ;
3116 daemon_log(" ", 1);
3117 daemon_log("$0 started!", 1);
3118 daemon_log("status: $server_status", 1);
3119 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3121 # connect to incoming_db
3122 unlink($incoming_file_name);
3123 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3124 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3126 # connect to gosa-si job queue
3127 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3128 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3130 # connect to known_clients_db
3131 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3132 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3134 # connect to foreign_clients_db
3135 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3136 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3138 # connect to known_server_db
3139 unlink($known_server_file_name);
3140 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3141 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3143 # connect to login_usr_db
3144 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3145 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3147 # connect to fai_server_db and fai_release_db
3148 unlink($fai_server_file_name);
3149 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3150 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3152 unlink($fai_release_file_name);
3153 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3154 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3156 # connect to packages_list_db
3157 #unlink($packages_list_file_name);
3158 unlink($packages_list_under_construction);
3159 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3160 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3162 # connect to messaging_db
3163 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3164 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3167 # create xml object used for en/decrypting
3168 $xml = new XML::Simple();
3171 # foreign servers 
3172 my @foreign_server_list;
3174 # add foreign server from cfg file
3175 if ($foreign_server_string ne "") {
3176     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3177     foreach my $foreign_server (@cfg_foreign_server_list) {
3178         push(@foreign_server_list, $foreign_server);
3179     }
3182 # add foreign server from dns
3183 my @tmp_servers;
3184 if ( !$server_domain) {
3185     # Try our DNS Searchlist
3186     for my $domain(get_dns_domains()) {
3187         chomp($domain);
3188         my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3189         if(@$tmp_domains) {
3190             for my $tmp_server(@$tmp_domains) {
3191                 push @tmp_servers, $tmp_server;
3192             }
3193         }
3194     }
3195     if(@tmp_servers && length(@tmp_servers)==0) {
3196         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3197     }
3198 } else {
3199     @tmp_servers = &get_server_addresses($server_domain);
3200     if( 0 == @tmp_servers ) {
3201         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3202     }
3204 foreach my $server (@tmp_servers) { 
3205     unshift(@foreign_server_list, $server); 
3207 # eliminate duplicate entries
3208 @foreign_server_list = &del_doubles(@foreign_server_list);
3209 my $all_foreign_server = join(", ", @foreign_server_list);
3210 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3212 # add all found foreign servers to known_server
3213 my $act_timestamp = &get_time();
3214 foreach my $foreign_server (@foreign_server_list) {
3216         # do not add myself to known_server_db
3217         if (&is_local($foreign_server)) { next; }
3218         ######################################
3220     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3221             primkey=>['hostname'],
3222             hostname=>$foreign_server,
3223             macaddress=>"",
3224             status=>'not_jet_registered',
3225             hostkey=>"none",
3226             loaded_modules => "none", 
3227             timestamp=>$act_timestamp,
3228             } );
3232 # Import all modules
3233 &import_modules;
3235 # Check wether all modules are gosa-si valid passwd check
3236 &password_check;
3238 # Prepare for using Opsi 
3239 if ($opsi_enabled eq "true") {
3240     use JSON::RPC::Client;
3241     use XML::Quote qw(:all);
3242     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3243     $opsi_client = new JSON::RPC::Client;
3247 POE::Component::Server::TCP->new(
3248     Alias => "TCP_SERVER",
3249         Port => $server_port,
3250         ClientInput => sub {
3251         my ($kernel, $input) = @_[KERNEL, ARG0];
3252         push(@tasks, $input);
3253         push(@msgs_to_decrypt, $input);
3254         $kernel->yield("msg_to_decrypt");
3255         },
3256     InlineStates => {
3257         msg_to_decrypt => \&msg_to_decrypt,
3258         next_task => \&next_task,
3259         task_result => \&handle_task_result,
3260         task_done   => \&handle_task_done,
3261         task_debug  => \&handle_task_debug,
3262         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3263     }
3264 );
3266 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3268 # create session for repeatedly checking the job queue for jobs
3269 POE::Session->create(
3270         inline_states => {
3271                 _start => \&session_start,
3272         register_at_foreign_servers => \&register_at_foreign_servers,
3273         sig_handler => \&sig_handler,
3274         next_task => \&next_task,
3275         task_result => \&handle_task_result,
3276         task_done   => \&handle_task_done,
3277         task_debug  => \&handle_task_debug,
3278         watch_for_next_tasks => \&watch_for_next_tasks,
3279         watch_for_new_messages => \&watch_for_new_messages,
3280         watch_for_delivery_messages => \&watch_for_delivery_messages,
3281         watch_for_done_messages => \&watch_for_done_messages,
3282                 watch_for_new_jobs => \&watch_for_new_jobs,
3283         watch_for_modified_jobs => \&watch_for_modified_jobs,
3284         watch_for_done_jobs => \&watch_for_done_jobs,
3285         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3286         watch_for_old_known_clients => \&watch_for_old_known_clients,
3287         create_packages_list_db => \&run_create_packages_list_db,
3288         create_fai_server_db => \&run_create_fai_server_db,
3289         create_fai_release_db => \&run_create_fai_release_db,
3290                 recreate_packages_db => \&run_recreate_packages_db,
3291         session_run_result => \&session_run_result,
3292         session_run_debug => \&session_run_debug,
3293         session_run_done => \&session_run_done,
3294         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3295         }
3296 );
3299 POE::Kernel->run();
3300 exit;