Code

* gosa-si-server-nobus
[gosa.git] / gosa-si / gosa-si-server-nobus
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
70 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($pid_file, $procid, $pid, $log_file);
76 my ($arp_activ, $arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file, 
85     $server_ip, $server_port, $ClientPackages_key, 
86     $arp_activ, $gosa_unit_tag,
87     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $bus_address;
95 our $gosa_address;
96 our $no_bus;
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);
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_bus = 0;
114 $bus_activ = "true";
115 $no_arp = 0;
116 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
117 my @packages_list_statements;
118 my $watch_for_new_jobs_in_progress = 0;
120 # holds all incoming decrypted messages
121 our $incoming_db;
122 our $incoming_tn = 'incoming';
123 my $incoming_file_name;
124 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
125         "timestamp DEFAULT 'none'", 
126         "headertag DEFAULT 'none'",
127                 "targettag DEFAULT 'none'",
128         "xmlmessage DEFAULT 'none'",
129         "module DEFAULT 'none'",
130         );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
137                 "timestamp DEFAULT 'none'", 
138                 "status DEFAULT 'none'", 
139                 "result DEFAULT 'none'", 
140                 "progress DEFAULT 'none'", 
141         "headertag DEFAULT 'none'", 
142                 "targettag DEFAULT 'none'", 
143                 "xmlmessage DEFAULT 'none'", 
144                 "macaddress DEFAULT 'none'",
145                 "plainname DEFAULT 'none'",
146                 );
148 # holds all other gosa-sd as well as the gosa-sd-bus
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients"; 
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client 
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server"; 
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release"; 
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386"; 
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging"; 
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
195         "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai"; 
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 %cfg_defaults = (
208 "general" => {
209     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
210     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
211     },
212 "bus" => {
213     "activ" => [\$bus_activ, "true"],
214     },
215 "server" => {
216     "port" => [\$server_port, "20081"],
217     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
218     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
219     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
220     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
221     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
222     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
223     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
224     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
225     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
226     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
227     "repo-path"            => [\$repo_path, '/srv/www/repository'],
228     "ldap-uri"             => [\$ldap_uri, ""],
229     "ldap-base"            => [\$ldap_base, ""],
230     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
231     "ldap-admin-password"  => [\$ldap_admin_password, ""],
232     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
233     "max-clients"          => [\$max_clients, 10],
234     },
235 "GOsaPackages" => {
236     "ip" => [\$gosa_ip, "0.0.0.0"],
237     "port" => [\$gosa_port, "20082"],
238     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
239     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
240     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
241     "key" => [\$GosaPackages_key, "none"],
242     },
243 "ClientPackages" => {
244     "key" => [\$ClientPackages_key, "none"],
245     },
246 "ServerPackages"=> {
247     "address"      => [\$foreign_server_string, ""],
248     "domain"  => [\$server_domain, ""],
249     "key"     => [\$ServerPackages_key, "none"],
250     "key-lifetime" => [\$foreign_servers_register_delay, 120],
252 );
255 #===  FUNCTION  ================================================================
256 #         NAME:  usage
257 #   PARAMETERS:  nothing
258 #      RETURNS:  nothing
259 #  DESCRIPTION:  print out usage text to STDERR
260 #===============================================================================
261 sub usage {
262     print STDERR << "EOF" ;
263 usage: $prg [-hvf] [-c config]
265            -h        : this (help) message
266            -c <file> : config file
267            -f        : foreground, process will not be forked to background
268            -v        : be verbose (multiple to increase verbosity)
269            -no-bus   : starts $prg without connection to bus
270            -no-arp   : starts $prg without connection to arp module
271  
272 EOF
273     print "\n" ;
277 #===  FUNCTION  ================================================================
278 #         NAME:  read_configfile
279 #   PARAMETERS:  cfg_file - string -
280 #      RETURNS:  nothing
281 #  DESCRIPTION:  read cfg_file and set variables
282 #===============================================================================
283 sub read_configfile {
284     my $cfg;
285     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
286         if( -r $cfg_file ) {
287             $cfg = Config::IniFiles->new( -file => $cfg_file );
288         } else {
289             print STDERR "Couldn't read config file!\n";
290         }
291     } else {
292         $cfg = Config::IniFiles->new() ;
293     }
294     foreach my $section (keys %cfg_defaults) {
295         foreach my $param (keys %{$cfg_defaults{ $section }}) {
296             my $pinfo = $cfg_defaults{ $section }{ $param };
297             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
298         }
299     }
303 #===  FUNCTION  ================================================================
304 #         NAME:  logging
305 #   PARAMETERS:  level - string - default 'info'
306 #                msg - string -
307 #                facility - string - default 'LOG_DAEMON'
308 #      RETURNS:  nothing
309 #  DESCRIPTION:  function for logging
310 #===============================================================================
311 sub daemon_log {
312     # log into log_file
313     my( $msg, $level ) = @_;
314     if(not defined $msg) { return }
315     if(not defined $level) { $level = 1 }
316     if(defined $log_file){
317         open(LOG_HANDLE, ">>$log_file");
318         chmod 0600, $log_file;
319         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
320             print STDERR "cannot open $log_file: $!";
321             return }
322             chomp($msg);
323                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
324             if($level <= $verbose){
325                 my ($seconds, $minutes, $hours, $monthday, $month,
326                         $year, $weekday, $yearday, $sommertime) = localtime(time);
327                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
328                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
329                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
330                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
331                 $month = $monthnames[$month];
332                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
333                 $year+=1900;
334                 my $name = $prg;
336                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
337                 print LOG_HANDLE $log_msg;
338                 if( $foreground ) { 
339                     print STDERR $log_msg;
340                 }
341             }
342         close( LOG_HANDLE );
343     }
347 #===  FUNCTION  ================================================================
348 #         NAME:  check_cmdline_param
349 #   PARAMETERS:  nothing
350 #      RETURNS:  nothing
351 #  DESCRIPTION:  validates commandline parameter
352 #===============================================================================
353 sub check_cmdline_param () {
354     my $err_config;
355     my $err_counter = 0;
356         if(not defined($cfg_file)) {
357                 $cfg_file = "/etc/gosa-si/server.conf";
358                 if(! -r $cfg_file) {
359                         $err_config = "please specify a config file";
360                         $err_counter += 1;
361                 }
362     }
363     if( $err_counter > 0 ) {
364         &usage( "", 1 );
365         if( defined( $err_config)) { print STDERR "$err_config\n"}
366         print STDERR "\n";
367         exit( -1 );
368     }
372 #===  FUNCTION  ================================================================
373 #         NAME:  check_pid
374 #   PARAMETERS:  nothing
375 #      RETURNS:  nothing
376 #  DESCRIPTION:  handels pid processing
377 #===============================================================================
378 sub check_pid {
379     $pid = -1;
380     # Check, if we are already running
381     if( open(LOCK_FILE, "<$pid_file") ) {
382         $pid = <LOCK_FILE>;
383         if( defined $pid ) {
384             chomp( $pid );
385             if( -f "/proc/$pid/stat" ) {
386                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
387                 if( $stat ) {
388                                         daemon_log("ERROR: Already running",1);
389                     close( LOCK_FILE );
390                     exit -1;
391                 }
392             }
393         }
394         close( LOCK_FILE );
395         unlink( $pid_file );
396     }
398     # create a syslog msg if it is not to possible to open PID file
399     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
400         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
401         if (open(LOCK_FILE, '<', $pid_file)
402                 && ($pid = <LOCK_FILE>))
403         {
404             chomp($pid);
405             $msg .= "(PID $pid)\n";
406         } else {
407             $msg .= "(unable to read PID)\n";
408         }
409         if( ! ($foreground) ) {
410             openlog( $0, "cons,pid", "daemon" );
411             syslog( "warning", $msg );
412             closelog();
413         }
414         else {
415             print( STDERR " $msg " );
416         }
417         exit( -1 );
418     }
421 #===  FUNCTION  ================================================================
422 #         NAME:  import_modules
423 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
424 #                are stored
425 #      RETURNS:  nothing
426 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
427 #                state is on is imported by "require 'file';"
428 #===============================================================================
429 sub import_modules {
430     daemon_log(" ", 1);
432     if (not -e $modules_path) {
433         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
434     }
436     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
437     while (defined (my $file = readdir (DIR))) {
438         if (not $file =~ /(\S*?).pm$/) {
439             next;
440         }
441                 my $mod_name = $1;
443         if( $file =~ /ArpHandler.pm/ ) {
444             if( $no_arp > 0 ) {
445                 next;
446             }
447         }
448         
449         eval { require $file; };
450         if ($@) {
451             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
452             daemon_log("$@", 5);
453                 } else {
454                         my $info = eval($mod_name.'::get_module_info()');
455                         # Only load module if get_module_info() returns a non-null object
456                         if( $info ) {
457                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
458                                 $known_modules->{$mod_name} = $info;
459                                 daemon_log("0 INFO: module $mod_name loaded", 5);
460                         }
461                 }
462     }   
463     close (DIR);
467 #===  FUNCTION  ================================================================
468 #         NAME:  sig_int_handler
469 #   PARAMETERS:  signal - string - signal arose from system
470 #      RETURNS:  noting
471 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
472 #===============================================================================
473 sub sig_int_handler {
474     my ($signal) = @_;
476 #       if (defined($ldap_handle)) {
477 #               $ldap_handle->disconnect;
478 #       }
479     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
480     
482     daemon_log("shutting down gosa-si-server", 1);
483     system("kill `ps -C gosa-si-server-nobus -o pid=`");
485 $SIG{INT} = \&sig_int_handler;
488 sub check_key_and_xml_validity {
489     my ($crypted_msg, $module_key, $session_id) = @_;
490     my $msg;
491     my $msg_hash;
492     my $error_string;
493     eval{
494         $msg = &decrypt_msg($crypted_msg, $module_key);
496         if ($msg =~ /<xml>/i){
497             $msg =~ s/\s+/ /g;  # just for better daemon_log
498             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
499             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
501             ##############
502             # check header
503             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
504             my $header_l = $msg_hash->{'header'};
505             if( 1 > @{$header_l} ) { die 'empty header tag'; }
506             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
507             my $header = @{$header_l}[0];
508             if( 0 == length $header) { die 'empty string in header tag'; }
510             ##############
511             # check source
512             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
513             my $source_l = $msg_hash->{'source'};
514             if( 1 > @{$source_l} ) { die 'empty source tag'; }
515             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
516             my $source = @{$source_l}[0];
517             if( 0 == length $source) { die 'source error'; }
519             ##############
520             # check target
521             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
522             my $target_l = $msg_hash->{'target'};
523             if( 1 > @{$target_l} ) { die 'empty target tag'; }
524         }
525     };
526     if($@) {
527         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
528         $msg = undef;
529         $msg_hash = undef;
530     }
532     return ($msg, $msg_hash);
536 sub check_outgoing_xml_validity {
537     my ($msg) = @_;
539     my $msg_hash;
540     eval{
541         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
543         ##############
544         # check header
545         my $header_l = $msg_hash->{'header'};
546         if( 1 != @{$header_l} ) {
547             die 'no or more than one headers specified';
548         }
549         my $header = @{$header_l}[0];
550         if( 0 == length $header) {
551             die 'header has length 0';
552         }
554         ##############
555         # check source
556         my $source_l = $msg_hash->{'source'};
557         if( 1 != @{$source_l} ) {
558             die 'no or more than 1 sources specified';
559         }
560         my $source = @{$source_l}[0];
561         if( 0 == length $source) {
562             die 'source has length 0';
563         }
564         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
565                 $source =~ /^GOSA$/i ) {
566             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
567         }
568         
569         ##############
570         # check target  
571         my $target_l = $msg_hash->{'target'};
572         if( 0 == @{$target_l} ) {
573             die "no targets specified";
574         }
575         foreach my $target (@$target_l) {
576             if( 0 == length $target) {
577                 die "target has length 0";
578             }
579             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
580                     $target =~ /^GOSA$/i ||
581                     $target =~ /^\*$/ ||
582                     $target =~ /KNOWN_SERVER/i ||
583                     $target =~ /JOBDB/i ||
584                     $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 ){
585                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
586             }
587         }
588     };
589     if($@) {
590         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
591         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
592         $msg_hash = undef;
593     }
595     return ($msg_hash);
599 sub input_from_known_server {
600     my ($input, $remote_ip, $session_id) = @_ ;  
601     my ($msg, $msg_hash, $module);
603     my $sql_statement= "SELECT * FROM known_server";
604     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
606     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
607         my $host_name = $hit->{hostname};
608         if( not $host_name =~ "^$remote_ip") {
609             next;
610         }
611         my $host_key = $hit->{hostkey};
612         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
613         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
615         # check if module can open msg envelope with module key
616         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
617         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
618             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
619             daemon_log("$@", 8);
620             next;
621         }
622         else {
623             $msg = $tmp_msg;
624             $msg_hash = $tmp_msg_hash;
625             $module = "ServerPackages";
626             last;
627         }
628     }
630     if( (!$msg) || (!$msg_hash) || (!$module) ) {
631         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
632     }
633   
634     return ($msg, $msg_hash, $module);
638 sub input_from_known_client {
639     my ($input, $remote_ip, $session_id) = @_ ;  
640     my ($msg, $msg_hash, $module);
642     my $sql_statement= "SELECT * FROM known_clients";
643     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
644     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
645         my $host_name = $hit->{hostname};
646         if( not $host_name =~ /^$remote_ip:\d*$/) {
647                 next;
648                 }
649         my $host_key = $hit->{hostkey};
650         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
651         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
653         # check if module can open msg envelope with module key
654         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
656         if( (!$msg) || (!$msg_hash) ) {
657             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
658             &daemon_log("$@", 8);
659             next;
660         }
661         else {
662             $module = "ClientPackages";
663             last;
664         }
665     }
667     if( (!$msg) || (!$msg_hash) || (!$module) ) {
668         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
669     }
671     return ($msg, $msg_hash, $module);
675 sub input_from_unknown_host {
676     no strict "refs";
677     my ($input, $session_id) = @_ ;
678     my ($msg, $msg_hash, $module);
679     my $error_string;
680     
681         my %act_modules = %$known_modules;
682         
683     while( my ($mod, $info) = each(%act_modules)) {
685         # check a key exists for this module
686         my $module_key = ${$mod."_key"};
687         if( not defined $module_key ) {
688             if( $mod eq 'ArpHandler' ) {
689                 next;
690             }
691             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
692             next;
693         }
694         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
696         # check if module can open msg envelope with module key
697         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
698         if( (not defined $msg) || (not defined $msg_hash) ) {
699             next;
700         }
701         else {
702             $module = $mod;
703             last;
704         }
705     }
707     if( (!$msg) || (!$msg_hash) || (!$module)) {
708         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
709     }
711     return ($msg, $msg_hash, $module);
715 sub create_ciphering {
716     my ($passwd) = @_;
717         if((!defined($passwd)) || length($passwd)==0) {
718                 $passwd = "";
719         }
720     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
721     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
722     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
723     $my_cipher->set_iv($iv);
724     return $my_cipher;
728 sub encrypt_msg {
729     my ($msg, $key) = @_;
730     my $my_cipher = &create_ciphering($key);
731     my $len;
732     {
733             use bytes;
734             $len= 16-length($msg)%16;
735     }
736     $msg = "\0"x($len).$msg;
737     $msg = $my_cipher->encrypt($msg);
738     chomp($msg = &encode_base64($msg));
739     # there are no newlines allowed inside msg
740     $msg=~ s/\n//g;
741     return $msg;
745 sub decrypt_msg {
747     my ($msg, $key) = @_ ;
748     $msg = &decode_base64($msg);
749     my $my_cipher = &create_ciphering($key);
750     $msg = $my_cipher->decrypt($msg); 
751     $msg =~ s/\0*//g;
752     return $msg;
756 sub get_encrypt_key {
757     my ($target) = @_ ;
758     my $encrypt_key;
759     my $error = 0;
761     # target can be in known_server
762     if( not defined $encrypt_key ) {
763         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
764         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
765         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
766             my $host_name = $hit->{hostname};
767             if( $host_name ne $target ) {
768                 next;
769             }
770             $encrypt_key = $hit->{hostkey};
771             last;
772         }
773     }
775     # target can be in known_client
776     if( not defined $encrypt_key ) {
777         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
778         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
779         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
780             my $host_name = $hit->{hostname};
781             if( $host_name ne $target ) {
782                 next;
783             }
784             $encrypt_key = $hit->{hostkey};
785             last;
786         }
787     }
789     return $encrypt_key;
793 #===  FUNCTION  ================================================================
794 #         NAME:  open_socket
795 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
796 #                [PeerPort] string necessary if port not appended by PeerAddr
797 #      RETURNS:  socket IO::Socket::INET
798 #  DESCRIPTION:  open a socket to PeerAddr
799 #===============================================================================
800 sub open_socket {
801     my ($PeerAddr, $PeerPort) = @_ ;
802     if(defined($PeerPort)){
803         $PeerAddr = $PeerAddr.":".$PeerPort;
804     }
805     my $socket;
806     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
807             Porto => "tcp",
808             Type => SOCK_STREAM,
809             Timeout => 5,
810             );
811     if(not defined $socket) {
812         return;
813     }
814 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
815     return $socket;
819 #===  FUNCTION  ================================================================
820 #         NAME:  get_ip 
821 #   PARAMETERS:  interface name (i.e. eth0)
822 #      RETURNS:  (ip address) 
823 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
824 #===============================================================================
825 sub get_ip {
826         my $ifreq= shift;
827         my $result= "";
828         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
829         my $proto= getprotobyname('ip');
831         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
832                 or die "socket: $!";
834         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
835                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
836                 my ($port, $addr) = sockaddr_in $sin;
837                 my $ip            = inet_ntoa $addr;
839                 if ($ip && length($ip) > 0) {
840                         $result = $ip;
841                 }
842         }
844         return $result;
848 sub get_local_ip_for_remote_ip {
849         my $remote_ip= shift;
850         my $result="0.0.0.0";
852         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
853                 if($remote_ip eq "127.0.0.1") {
854                         $result = "127.0.0.1";
855                 } else {
856                         my $PROC_NET_ROUTE= ('/proc/net/route');
858                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
859                                 or die "Could not open $PROC_NET_ROUTE";
861                         my @ifs = <PROC_NET_ROUTE>;
863                         close(PROC_NET_ROUTE);
865                         # Eat header line
866                         shift @ifs;
867                         chomp @ifs;
868                         foreach my $line(@ifs) {
869                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
870                                 my $destination;
871                                 my $mask;
872                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
873                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
875                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
877                                         # destination matches route, save mac and exit
878                                         $result= &get_ip($Iface);
879                                         last;
880                                 }
881                         }
882                 }
883         } else {
884                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
885         }
886         return $result;
890 sub send_msg_to_target {
891     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
892     my $error = 0;
893     my $header;
894     my $new_status;
895     my $act_status;
896     my ($sql_statement, $res);
897   
898     if( $msg_header ) {
899         $header = "'$msg_header'-";
900     } else {
901         $header = "";
902     }
904         # Patch the source ip
905         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
906                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
907                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
908         }
910     # encrypt xml msg
911     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
913     # opensocket
914     my $socket = &open_socket($address);
915     if( !$socket ) {
916         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
917         $error++;
918     }
919     
920     if( $error == 0 ) {
921         # send xml msg
922         print $socket $crypted_msg."\n";
924         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
925         daemon_log("DEBUG: message:\n$msg", 9);
926         
927     }
929     # close socket in any case
930     if( $socket ) {
931         close $socket;
932     }
934     if( $error > 0 ) { $new_status = "down"; }
935     else { $new_status = $msg_header; }
938     # known_clients
939     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
940     $res = $known_clients_db->select_dbentry($sql_statement);
941     if( keys(%$res) > 0) {
942         $act_status = $res->{1}->{'status'};
943         if ($act_status eq "down" && $new_status eq "down") {
944             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
945             $res = $known_clients_db->del_dbentry($sql_statement);
946             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
947         } else { 
948             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
949             $res = $known_clients_db->update_dbentry($sql_statement);
950             if($new_status eq "down"){
951                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
952             } else {
953                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
954             }
955         }
956     }
958     # known_server
959     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
960     $res = $known_server_db->select_dbentry($sql_statement);
961     if( keys(%$res) > 0 ) {
962         $act_status = $res->{1}->{'status'};
963         if ($act_status eq "down" && $new_status eq "down") {
964             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
965             $res = $known_server_db->del_dbentry($sql_statement);
966             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
967         } 
968         else { 
969             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
970             $res = $known_server_db->update_dbentry($sql_statement);
971             if($new_status eq "down"){
972                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
973             }
974             else {
975                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
976             }
977         }
978     }
979     return $error; 
983 sub update_jobdb_status_for_send_msgs {
984     my ($answer, $error) = @_;
985     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
986         my $jobdb_id = $1;
987             
988         # sending msg faild
989         if( $error ) {
990             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
991                 my $sql_statement = "UPDATE $job_queue_tn ".
992                     "SET status='error', result='can not deliver msg, please consult log file' ".
993                     "WHERE id=$jobdb_id";
994                 my $res = $job_db->update_dbentry($sql_statement);
995             }
997         # sending msg was successful
998         } else {
999             my $sql_statement = "UPDATE $job_queue_tn ".
1000                 "SET status='done' ".
1001                 "WHERE id=$jobdb_id AND status='processed'";
1002             my $res = $job_db->update_dbentry($sql_statement);
1003         }
1004     }
1007 sub _start {
1008     my ($kernel) = $_[KERNEL];
1009     &trigger_db_loop($kernel);
1010     $global_kernel = $kernel;
1011     $kernel->yield('register_at_foreign_servers');
1012         $kernel->yield('create_fai_server_db', $fai_server_tn );
1013         $kernel->yield('create_fai_release_db', $fai_release_tn );
1014         $kernel->sig(USR1 => "sig_handler");
1015         $kernel->sig(USR2 => "create_packages_list_db");
1018 sub sig_handler {
1019         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1020         daemon_log("0 INFO got signal '$signal'", 1); 
1021         $kernel->sig_handled();
1022         return;
1026 sub msg_to_decrypt {
1027     my ($session, $heap) = @_[SESSION, HEAP];
1028     my $session_id = $session->ID;
1029     my ($msg, $msg_hash, $module);
1030     my $error = 0;
1032     # hole neue msg aus @msgs_to_decrypt
1033     my $next_msg = shift @msgs_to_decrypt;
1034     
1035     # entschlüssle sie
1037     # msg is from a new client or gosa
1038     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1039     # msg is from a gosa-si-server or gosa-si-bus
1040     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1042     }
1043     # msg is from a gosa-si-client
1044     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1045         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1046     }
1047     # an error occurred
1048     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1050         # could not understand a msg from its server the client cause a re-registering process
1051         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1052         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1053         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1054         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1055             my $host_name = $hit->{'hostname'};
1056             my $host_key = $hit->{'hostkey'};
1057             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1058             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1059             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1060         }
1061         $error++;
1062     }
1063     
1064     # add message to incoming_db
1065     if( $error == 0) {
1066         my $header = @{$msg_hash->{'header'}}[0];
1067         my $target = @{$msg_hash->{'target'}}[0];
1068         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1069                 primkey=>[],
1070                 headertag=>$header,
1071                                 targettag=>$target,
1072                 xmlmessage=>$msg,
1073                 timestamp=>&get_time,
1074                 module=>$module,
1075                 } );
1076         if ($res != 0) {
1077                         # TODO ist das mit $! so ok???
1078             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1079         }
1080     }
1085 sub next_task {
1086     my ($session, $heap) = @_[SESSION, HEAP];
1087     my $task = POE::Wheel::Run->new(
1088             Program => sub { process_task($session, $heap) },
1089             StdioFilter => POE::Filter::Reference->new(),
1090             StdoutEvent  => "task_result",
1091             StderrEvent  => "task_debug",
1092             CloseEvent   => "task_done",
1093             );
1095     $heap->{task}->{ $task->ID } = $task;
1098 sub handle_task_result {
1099     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1100     my $client_answer = $result->{'answer'};
1101     if( $client_answer =~ s/session_id=(\d+)$// ) {
1102         my $session_id = $1;
1103         if( defined $session_id ) {
1104             my $session_reference = $kernel->ID_id_to_session($session_id);
1105             if( defined $session_reference ) {
1106                 $heap = $session_reference->get_heap();
1107             }
1108         }
1110         if(exists $heap->{'client'}) {
1111             $heap->{'client'}->put($client_answer);
1112         }
1113     }
1114     $kernel->sig(CHLD => "child_reap");
1117 sub handle_task_debug {
1118     my $result = $_[ARG0];
1119     print STDERR "$result\n";
1122 sub handle_task_done {
1123     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1124     delete $heap->{task}->{$task_id};
1127 sub process_task {
1128     no strict "refs";
1129     my ($session, $heap, $input) = @_;
1130     my $session_id = $session->ID;
1131     my $error = 0;
1132     my $answer_l;
1133     my ($answer_header, @answer_target_l, $answer_source);
1134     my $client_answer = "";
1136         ##################################################
1137         # fetch first unprocessed message from incoming_db
1138     # sometimes the program is faster than sqlite, so wait until informations are present at db
1139     my $id_sql;
1140     my $id_res;
1141     my $message_id;
1142 # TODO : das hier ist sehr sehr unschön        
1143 # to be tested: speed enhancement with usleep 100000???
1144     while (1) {
1145         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1146         $id_res = $incoming_db->exec_statement($id_sql);
1147         $message_id = @{@$id_res[0]}[0];
1148         if (defined $message_id) { last }
1149         usleep(100000);
1150     }
1152     # fetch new message from incoming_db
1153     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1154     my $res = $incoming_db->exec_statement($sql);
1156     # prepare all variables needed to process message
1157     my $msg = @{@$res[0]}[4];
1158     my $incoming_id = @{@$res[0]}[0];
1159     my $module = @{@$res[0]}[5];
1160     my $header =  @{@$res[0]}[2];
1161     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1163     # messages which are an answer to a still running process should not be processed here
1164     if ($header =~ /^answer_(\d+)/) {
1165         return;
1166     }
1167    
1168     # delete message from db 
1169     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1170     my $delete_res = $incoming_db->exec_statement($delete_sql);
1172     ######################
1173     # process incoming msg
1174     if( $error == 0) {
1175         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1176                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1177         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1178         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1180         if ( 0 < @{$answer_l} ) {
1181             my $answer_str = join("\n", @{$answer_l});
1182             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1183                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1184             }
1185             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1186         } else {
1187             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1188         }
1190     }
1191     if( !$answer_l ) { $error++ };
1193     ########
1194     # answer
1195     if( $error == 0 ) {
1197         foreach my $answer ( @{$answer_l} ) {
1198             # check outgoing msg to xml validity
1199             my $answer_hash = &check_outgoing_xml_validity($answer);
1200             if( not defined $answer_hash ) { next; }
1201             
1202             $answer_header = @{$answer_hash->{'header'}}[0];
1203             @answer_target_l = @{$answer_hash->{'target'}};
1204             $answer_source = @{$answer_hash->{'source'}}[0];
1206             # deliver msg to all targets 
1207             foreach my $answer_target ( @answer_target_l ) {
1209                 # targets of msg are all gosa-si-clients in known_clients_db
1210                 if( $answer_target eq "*" ) {
1211                     # answer is for all clients
1212                     my $sql_statement= "SELECT * FROM known_clients";
1213                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1214                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1215                         my $host_name = $hit->{hostname};
1216                         my $host_key = $hit->{hostkey};
1217                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1218                         &update_jobdb_status_for_send_msgs($answer, $error);
1219                     }
1220                 }
1222                 # targets of msg are all gosa-si-server in known_server_db
1223                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1224                     # answer is for all server in known_server
1225                     my $sql_statement= "SELECT * FROM known_server";
1226                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1227                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1228                         my $host_name = $hit->{hostname};
1229                         my $host_key = $hit->{hostkey};
1230                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1231                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1232                         &update_jobdb_status_for_send_msgs($answer, $error);
1233                     }
1234                 }
1236                 # target of msg is GOsa
1237                                 elsif( $answer_target eq "GOSA" ) {
1238                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1239                                         my $add_on = "";
1240                     if( defined $session_id ) {
1241                         $add_on = ".session_id=$session_id";
1242                     }
1243                     # answer is for GOSA and has to returned to connected client
1244                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1245                     $client_answer = $gosa_answer.$add_on;
1246                 }
1248                 # target of msg is job queue at this host
1249                 elsif( $answer_target eq "JOBDB") {
1250                     $answer =~ /<header>(\S+)<\/header>/;   
1251                     my $header;
1252                     if( defined $1 ) { $header = $1; }
1253                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1254                     &update_jobdb_status_for_send_msgs($answer, $error);
1255                 }
1257                 # target of msg is a mac address
1258                 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 ) {
1259                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1260                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1261                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1262                     my $found_ip_flag = 0;
1263                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1264                         my $host_name = $hit->{hostname};
1265                         my $host_key = $hit->{hostkey};
1266                         $answer =~ s/$answer_target/$host_name/g;
1267                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1268                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1269                         &update_jobdb_status_for_send_msgs($answer, $error);
1270                         $found_ip_flag++ ;
1271                     }   
1272                     if( $found_ip_flag == 0) {
1273                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1274                         if( $bus_activ eq "true" ) { 
1275                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1276                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1277                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1278                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1279                                 my $bus_address = $hit->{hostname};
1280                                 my $bus_key = $hit->{hostkey};
1281                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1282                                 &update_jobdb_status_for_send_msgs($answer, $error);
1283                                 last;
1284                             }
1285                         }
1287                     }
1289                 #  answer is for one specific host   
1290                 } else {
1291                     # get encrypt_key
1292                     my $encrypt_key = &get_encrypt_key($answer_target);
1293                     if( not defined $encrypt_key ) {
1294                         # unknown target, forward msg to bus
1295                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1296                         if( $bus_activ eq "true" ) { 
1297                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1298                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1299                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1300                             my $res_length = keys( %{$query_res} );
1301                             if( $res_length == 0 ){
1302                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1303                                         "no bus found in known_server", 3);
1304                             }
1305                             else {
1306                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1307                                     my $bus_key = $hit->{hostkey};
1308                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1309                                     &update_jobdb_status_for_send_msgs($answer, $error);
1310                                 }
1311                             }
1312                         }
1313                         next;
1314                     }
1315                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1316                     &update_jobdb_status_for_send_msgs($answer, $error);
1317                 }
1318             }
1319         }
1320     }
1322     my $filter = POE::Filter::Reference->new();
1323     my %result = ( 
1324             status => "seems ok to me",
1325             answer => $client_answer,
1326             );
1328     my $output = $filter->put( [ \%result ] );
1329     print @$output;
1335 sub trigger_db_loop {
1336         my ($kernel) = @_ ;
1337         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1338         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1339         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1340     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1341         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1342     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1346 sub watch_for_done_jobs {
1347     my ($kernel,$heap) = @_[KERNEL, HEAP];
1349     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1350         " WHERE status='done'";
1351         my $res = $job_db->select_dbentry( $sql_statement );
1353     while( my ($id, $hit) = each %{$res} ) {
1354         my $jobdb_id = $hit->{id};
1355         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1356         my $res = $job_db->del_dbentry($sql_statement); 
1357     }
1359     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1363 sub watch_for_new_jobs {
1364         if($watch_for_new_jobs_in_progress == 0) {
1365                 $watch_for_new_jobs_in_progress = 1;
1366                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1368                 # check gosa job queue for jobs with executable timestamp
1369                 my $timestamp = &get_time();
1370                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1371                 my $res = $job_db->exec_statement( $sql_statement );
1373                 # Merge all new jobs that would do the same actions
1374                 my @drops;
1375                 my $hits;
1376                 foreach my $hit (reverse @{$res} ) {
1377                         my $macaddress= lc @{$hit}[8];
1378                         my $headertag= @{$hit}[5];
1379                         if(
1380                                 defined($hits->{$macaddress}) &&
1381                                 defined($hits->{$macaddress}->{$headertag}) &&
1382                                 defined($hits->{$macaddress}->{$headertag}[0])
1383                         ) {
1384                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1385                         }
1386                         $hits->{$macaddress}->{$headertag}= $hit;
1387                 }
1389                 # Delete new jobs with a matching job in state 'processing'
1390                 foreach my $macaddress (keys %{$hits}) {
1391                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1392                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1393                                 if(defined($jobdb_id)) {
1394                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1395                                         my $res = $job_db->exec_statement( $sql_statement );
1396                                         foreach my $hit (@{$res}) {
1397                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1398                                         }
1399                                 } else {
1400                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1401                                 }
1402                         }
1403                 }
1405                 # Commit deletion
1406                 $job_db->exec_statementlist(\@drops);
1408                 # Look for new jobs that could be executed
1409                 foreach my $macaddress (keys %{$hits}) {
1411                         # Look if there is an executing job
1412                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1413                         my $res = $job_db->exec_statement( $sql_statement );
1415                         # Skip new jobs for host if there is a processing job
1416                         if(defined($res) and defined @{$res}[0]) {
1417                                 next;
1418                         }
1420                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1421                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1422                                 if(defined($jobdb_id)) {
1423                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1425                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1426                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1427                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1429                                         # expect macaddress is unique!!!!!!
1430                                         my $target = $res_hash->{1}->{hostname};
1432                                         # change header
1433                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1435                                         # add sqlite_id
1436                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1438                                         $job_msg =~ /<header>(\S+)<\/header>/;
1439                                         my $header = $1 ;
1440                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1442                                         # update status in job queue to 'processing'
1443                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1444                                         my $res = $job_db->update_dbentry($sql_statement);
1445 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1447                                         # We don't want parallel processing
1448                                         last;
1449                                 }
1450                         }
1451                 }
1453                 $watch_for_new_jobs_in_progress = 0;
1454                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1455         }
1459 sub watch_for_new_messages {
1460     my ($kernel,$heap) = @_[KERNEL, HEAP];
1461     my @coll_user_msg;   # collection list of outgoing messages
1462     
1463     # check messaging_db for new incoming messages with executable timestamp
1464     my $timestamp = &get_time();
1465     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1466     my $res = $messaging_db->exec_statement( $sql_statement );
1467         foreach my $hit (@{$res}) {
1469         # create outgoing messages
1470         my $message_to = @{$hit}[3];
1471         # translate message_to to plain login name
1472         my @message_to_l = split(/,/, $message_to);  
1473                 my %receiver_h; 
1474                 foreach my $receiver (@message_to_l) {
1475                         if ($receiver =~ /^u_([\s\S]*)$/) {
1476                                 $receiver_h{$1} = 0;
1477                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1478                                 my $group_name = $1;
1479                                 # fetch all group members from ldap and add them to receiver hash
1480                                 my $ldap_handle = &get_ldap_handle();
1481                                 if (defined $ldap_handle) {
1482                                                 my $mesg = $ldap_handle->search(
1483                                                                                 base => $ldap_base,
1484                                                                                 scope => 'sub',
1485                                                                                 attrs => ['memberUid'],
1486                                                                                 filter => "cn=$group_name",
1487                                                                                 );
1488                                                 if ($mesg->count) {
1489                                                                 my @entries = $mesg->entries;
1490                                                                 foreach my $entry (@entries) {
1491                                                                                 my @receivers= $entry->get_value("memberUid");
1492                                                                                 foreach my $receiver (@receivers) { 
1493                                                                                                 $receiver_h{$1} = 0;
1494                                                                                 }
1495                                                                 }
1496                                                 } 
1497                                                 # translating errors ?
1498                                                 if ($mesg->code) {
1499                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1500                                                 }
1501                                 # ldap handle error ?           
1502                                 } else {
1503                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1504                                 }
1505                         } else {
1506                                 my $sbjct = &encode_base64(@{$hit}[1]);
1507                                 my $msg = &encode_base64(@{$hit}[7]);
1508                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1509                         }
1510                 }
1511                 my @receiver_l = keys(%receiver_h);
1513         my $message_id = @{$hit}[0];
1515         #add each outgoing msg to messaging_db
1516         my $receiver;
1517         foreach $receiver (@receiver_l) {
1518             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1519                 "VALUES ('".
1520                 $message_id."', '".    # id
1521                 @{$hit}[1]."', '".     # subject
1522                 @{$hit}[2]."', '".     # message_from
1523                 $receiver."', '".      # message_to
1524                 "none"."', '".         # flag
1525                 "out"."', '".          # direction
1526                 @{$hit}[6]."', '".     # delivery_time
1527                 @{$hit}[7]."', '".     # message
1528                 $timestamp."'".     # timestamp
1529                 ")";
1530             &daemon_log("M DEBUG: $sql_statement", 1);
1531             my $res = $messaging_db->exec_statement($sql_statement);
1532             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1533         }
1535         # set incoming message to flag d=deliverd
1536         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1537         &daemon_log("M DEBUG: $sql_statement", 7);
1538         $res = $messaging_db->update_dbentry($sql_statement);
1539         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1540     }
1542     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1543     return;
1546 sub watch_for_delivery_messages {
1547     my ($kernel, $heap) = @_[KERNEL, HEAP];
1549     # select outgoing messages
1550     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1551     #&daemon_log("0 DEBUG: $sql", 7);
1552     my $res = $messaging_db->exec_statement( $sql_statement );
1553     
1554     # build out msg for each    usr
1555     foreach my $hit (@{$res}) {
1556         my $receiver = @{$hit}[3];
1557         my $msg_id = @{$hit}[0];
1558         my $subject = @{$hit}[1];
1559         my $message = @{$hit}[7];
1561         # resolve usr -> host where usr is logged in
1562         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1563         #&daemon_log("0 DEBUG: $sql", 7);
1564         my $res = $login_users_db->exec_statement($sql);
1566         # reciver is logged in nowhere
1567         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1569                 my $send_succeed = 0;
1570                 foreach my $hit (@$res) {
1571                                 my $receiver_host = @$hit[0];
1572                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1574                                 # fetch key to encrypt msg propperly for usr/host
1575                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1576                                 &daemon_log("0 DEBUG: $sql", 7);
1577                                 my $res = $known_clients_db->select_dbentry($sql);
1579                                 # host is already down
1580                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1582                                 # host is on
1583                                 my $receiver_key = @{@{$res}[0]}[2];
1584                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1585                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1586                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1587                                 if ($error == 0 ) {
1588                                         $send_succeed++ ;
1589                                 }
1590                 }
1592                 if ($send_succeed) {
1593                                 # set outgoing msg at db to deliverd
1594                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1595                                 &daemon_log("0 DEBUG: $sql", 7);
1596                                 my $res = $messaging_db->exec_statement($sql); 
1597                 }
1598         }
1600     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1601     return;
1605 sub watch_for_done_messages {
1606     my ($kernel,$heap) = @_[KERNEL, HEAP];
1608     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1609     #&daemon_log("0 DEBUG: $sql", 7);
1610     my $res = $messaging_db->exec_statement($sql); 
1612     foreach my $hit (@{$res}) {
1613         my $msg_id = @{$hit}[0];
1615         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1616         #&daemon_log("0 DEBUG: $sql", 7); 
1617         my $res = $messaging_db->exec_statement($sql);
1619         # not all usr msgs have been seen till now
1620         if ( ref(@$res[0]) eq "ARRAY") { next; }
1621         
1622         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1623         #&daemon_log("0 DEBUG: $sql", 7);
1624         $res = $messaging_db->exec_statement($sql);
1625     
1626     }
1628     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1629     return;
1633 sub watch_for_old_known_clients {
1634     my ($kernel,$heap) = @_[KERNEL, HEAP];
1636     my $sql_statement = "SELECT * FROM $known_clients_tn";
1637     my $res = $known_clients_db->select_dbentry( $sql_statement );
1639     my $act_time = int(&get_time());
1640     while ( my ($hit_num, $hit) = each %$res) {
1641         my $expired_timestamp = int($hit->{'timestamp'}) + (2 * int($hit->{'keylifetime'}));
1642         if ($act_time > $expired_timestamp) {
1643             my $hostname = $hit->{'hostname'};
1644             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1645             my $del_res = $known_clients_db->exec_statement($del_sql);
1647             &main::daemon_log("0 INFO: timestamp of client '$hostname' is expired, client will be deleted from known_clients_db", 5);
1648         }
1650     }
1652     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1656 sub get_ldap_handle {
1657         my ($session_id) = @_;
1658         my $heap;
1659         my $ldap_handle;
1661         if (not defined $session_id ) { $session_id = 0 };
1662         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1664         if ($session_id == 0) {
1665                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1666                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1667                 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password); 
1669         } else {
1670                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1671                 if( defined $session_reference ) {
1672                         $heap = $session_reference->get_heap();
1673                 }
1675                 if (not defined $heap) {
1676                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1677                         return;
1678                 }
1680                 # TODO: This "if" is nonsense, because it doesn't prove that the
1681                 #       used handle is still valid - or if we've to reconnect...
1682                 #if (not exists $heap->{ldap_handle}) {
1683                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1684                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1685                         $heap->{ldap_handle} = $ldap_handle;
1686                 #}
1687         }
1688         return $ldap_handle;
1692 sub change_fai_state {
1693     my ($st, $targets, $session_id) = @_;
1694     $session_id = 0 if not defined $session_id;
1695     # Set FAI state to localboot
1696     my %mapActions= (
1697         reboot    => '',
1698         update    => 'softupdate',
1699         localboot => 'localboot',
1700         reinstall => 'install',
1701         rescan    => '',
1702         wake      => '',
1703         memcheck  => 'memcheck',
1704         sysinfo   => 'sysinfo',
1705         install   => 'install',
1706     );
1708     # Return if this is unknown
1709     if (!exists $mapActions{ $st }){
1710         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1711       return;
1712     }
1714     my $state= $mapActions{ $st };
1716     my $ldap_handle = &get_ldap_handle($session_id);
1717     if( defined($ldap_handle) ) {
1719       # Build search filter for hosts
1720         my $search= "(&(objectClass=GOhard)";
1721         foreach (@{$targets}){
1722             $search.= "(macAddress=$_)";
1723         }
1724         $search.= ")";
1726       # If there's any host inside of the search string, procress them
1727         if (!($search =~ /macAddress/)){
1728             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1729             return;
1730         }
1732       # Perform search for Unit Tag
1733       my $mesg = $ldap_handle->search(
1734           base   => $ldap_base,
1735           scope  => 'sub',
1736           attrs  => ['dn', 'FAIstate', 'objectClass'],
1737           filter => "$search"
1738           );
1740           if ($mesg->count) {
1741                   my @entries = $mesg->entries;
1742                   foreach my $entry (@entries) {
1743                           # Only modify entry if it is not set to '$state'
1744                           if ($entry->get_value("FAIstate") ne "$state"){
1745                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1746                                   my $result;
1747                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1748                                   if (exists $tmp{'FAIobject'}){
1749                                           if ($state eq ''){
1750                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1751                                                           delete => [ FAIstate => [] ] ]);
1752                                           } else {
1753                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1754                                                           replace => [ FAIstate => $state ] ]);
1755                                           }
1756                                   } elsif ($state ne ''){
1757                                           $result= $ldap_handle->modify($entry->dn, changes => [
1758                                                   add     => [ objectClass => 'FAIobject' ],
1759                                                   add     => [ FAIstate => $state ] ]);
1760                                   }
1762                                   # Errors?
1763                                   if ($result->code){
1764                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1765                                   }
1766                           } else {
1767                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1768                           }  
1769                   }
1770           }
1771     # if no ldap handle defined
1772     } else {
1773         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1774     }
1779 sub change_goto_state {
1780     my ($st, $targets, $session_id) = @_;
1781     $session_id = 0  if not defined $session_id;
1783     # Switch on or off?
1784     my $state= $st eq 'active' ? 'active': 'locked';
1786     my $ldap_handle = &get_ldap_handle($session_id);
1787     if( defined($ldap_handle) ) {
1789       # Build search filter for hosts
1790       my $search= "(&(objectClass=GOhard)";
1791       foreach (@{$targets}){
1792         $search.= "(macAddress=$_)";
1793       }
1794       $search.= ")";
1796       # If there's any host inside of the search string, procress them
1797       if (!($search =~ /macAddress/)){
1798         return;
1799       }
1801       # Perform search for Unit Tag
1802       my $mesg = $ldap_handle->search(
1803           base   => $ldap_base,
1804           scope  => 'sub',
1805           attrs  => ['dn', 'gotoMode'],
1806           filter => "$search"
1807           );
1809       if ($mesg->count) {
1810         my @entries = $mesg->entries;
1811         foreach my $entry (@entries) {
1813           # Only modify entry if it is not set to '$state'
1814           if ($entry->get_value("gotoMode") ne $state){
1816             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1817             my $result;
1818             $result= $ldap_handle->modify($entry->dn, changes => [
1819                                                 replace => [ gotoMode => $state ] ]);
1821             # Errors?
1822             if ($result->code){
1823               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1824             }
1826           }
1827         }
1828       }
1830     }
1834 sub run_create_fai_server_db {
1835     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1836     my $session_id = $session->ID;
1837     my $task = POE::Wheel::Run->new(
1838             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1839             StdoutEvent  => "session_run_result",
1840             StderrEvent  => "session_run_debug",
1841             CloseEvent   => "session_run_done",
1842             );
1844     $heap->{task}->{ $task->ID } = $task;
1845     return;
1849 sub create_fai_server_db {
1850     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1851         my $result;
1853         if (not defined $session_id) { $session_id = 0; }
1854     my $ldap_handle = &get_ldap_handle();
1855         if(defined($ldap_handle)) {
1856                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1857                 my $mesg= $ldap_handle->search(
1858                         base   => $ldap_base,
1859                         scope  => 'sub',
1860                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1861                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1862                 );
1863                 if($mesg->{'resultCode'} == 0 &&
1864                    $mesg->count != 0) {
1865                    foreach my $entry (@{$mesg->{entries}}) {
1866                            if($entry->exists('FAIrepository')) {
1867                                    # Add an entry for each Repository configured for server
1868                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1869                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1870                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1871                                                    $result= $fai_server_db->add_dbentry( { 
1872                                                                    table => $table_name,
1873                                                                    primkey => ['server', 'release', 'tag'],
1874                                                                    server => $tmp_url,
1875                                                                    release => $tmp_release,
1876                                                                    sections => $tmp_sections,
1877                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1878                                                            } );
1879                                            }
1880                                    }
1881                            }
1882                    }
1883                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1885                 # TODO: Find a way to post the 'create_packages_list_db' event
1886                 if(not defined($dont_create_packages_list)) {
1887                         &create_packages_list_db(undef, undef, $session_id);
1888                 }
1889         }       
1890     
1891     $ldap_handle->disconnect;
1892         return $result;
1896 sub run_create_fai_release_db {
1897     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1898         my $session_id = $session->ID;
1899     my $task = POE::Wheel::Run->new(
1900             Program => sub { &create_fai_release_db($table_name, $session_id) },
1901             StdoutEvent  => "session_run_result",
1902             StderrEvent  => "session_run_debug",
1903             CloseEvent   => "session_run_done",
1904             );
1906     $heap->{task}->{ $task->ID } = $task;
1907     return;
1911 sub create_fai_release_db {
1912         my ($table_name, $session_id) = @_;
1913         my $result;
1915     # used for logging
1916     if (not defined $session_id) { $session_id = 0; }
1918     my $ldap_handle = &get_ldap_handle();
1919         if(defined($ldap_handle)) {
1920                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1921                 my $mesg= $ldap_handle->search(
1922                         base   => $ldap_base,
1923                         scope  => 'sub',
1924                         attrs  => [],
1925                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1926                 );
1927                 if($mesg->{'resultCode'} == 0 &&
1928                         $mesg->count != 0) {
1929                         # Walk through all possible FAI container ou's
1930                         my @sql_list;
1931                         my $timestamp= &get_time();
1932                         foreach my $ou (@{$mesg->{entries}}) {
1933                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1934                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1935                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1936                                         if(@tmp_array) {
1937                                                 foreach my $entry (@tmp_array) {
1938                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1939                                                                 my $sql= 
1940                                                                 "INSERT INTO $table_name "
1941                                                                 ."(timestamp, release, class, type, state) VALUES ("
1942                                                                 .$timestamp.","
1943                                                                 ."'".$entry->{'release'}."',"
1944                                                                 ."'".$entry->{'class'}."',"
1945                                                                 ."'".$entry->{'type'}."',"
1946                                                                 ."'".$entry->{'state'}."')";
1947                                                                 push @sql_list, $sql;
1948                                                         }
1949                                                 }
1950                                         }
1951                                 }
1952                         }
1954                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1955                         if(@sql_list) {
1956                                 unshift @sql_list, "VACUUM";
1957                                 unshift @sql_list, "DELETE FROM $table_name";
1958                                 $fai_release_db->exec_statementlist(\@sql_list);
1959                         }
1960                         daemon_log("$session_id DEBUG: Done with inserting",7);
1961                 }
1962                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1963         }
1964     $ldap_handle->disconnect;
1965         return $result;
1968 sub get_fai_types {
1969         my $tmp_classes = shift || return undef;
1970         my @result;
1972         foreach my $type(keys %{$tmp_classes}) {
1973                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1974                         my $entry = {
1975                                 type => $type,
1976                                 state => $tmp_classes->{$type}[0],
1977                         };
1978                         push @result, $entry;
1979                 }
1980         }
1982         return @result;
1985 sub get_fai_state {
1986         my $result = "";
1987         my $tmp_classes = shift || return $result;
1989         foreach my $type(keys %{$tmp_classes}) {
1990                 if(defined($tmp_classes->{$type}[0])) {
1991                         $result = $tmp_classes->{$type}[0];
1992                         
1993                 # State is equal for all types in class
1994                         last;
1995                 }
1996         }
1998         return $result;
2001 sub resolve_fai_classes {
2002         my ($fai_base, $ldap_handle, $session_id) = @_;
2003         if (not defined $session_id) { $session_id = 0; }
2004         my $result;
2005         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2006         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2007         my $fai_classes;
2009         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2010         my $mesg= $ldap_handle->search(
2011                 base   => $fai_base,
2012                 scope  => 'sub',
2013                 attrs  => ['cn','objectClass','FAIstate'],
2014                 filter => $fai_filter,
2015         );
2016         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2018         if($mesg->{'resultCode'} == 0 &&
2019                 $mesg->count != 0) {
2020                 foreach my $entry (@{$mesg->{entries}}) {
2021                         if($entry->exists('cn')) {
2022                                 my $tmp_dn= $entry->dn();
2024                                 # Skip classname and ou dn parts for class
2025                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2027                                 # Skip classes without releases
2028                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2029                                         next;
2030                                 }
2032                                 my $tmp_cn= $entry->get_value('cn');
2033                                 my $tmp_state= $entry->get_value('FAIstate');
2035                                 my $tmp_type;
2036                                 # Get FAI type
2037                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2038                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2039                                                 $tmp_type= $oclass;
2040                                                 last;
2041                                         }
2042                                 }
2044                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2045                                         # A Subrelease
2046                                         my @sub_releases = split(/,/, $tmp_release);
2048                                         # Walk through subreleases and build hash tree
2049                                         my $hash;
2050                                         while(my $tmp_sub_release = pop @sub_releases) {
2051                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2052                                         }
2053                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2054                                 } else {
2055                                         # A branch, no subrelease
2056                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2057                                 }
2058                         } elsif (!$entry->exists('cn')) {
2059                                 my $tmp_dn= $entry->dn();
2060                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2062                                 # Skip classes without releases
2063                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2064                                         next;
2065                                 }
2067                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2068                                         # A Subrelease
2069                                         my @sub_releases= split(/,/, $tmp_release);
2071                                         # Walk through subreleases and build hash tree
2072                                         my $hash;
2073                                         while(my $tmp_sub_release = pop @sub_releases) {
2074                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2075                                         }
2076                                         # Remove the last two characters
2077                                         chop($hash);
2078                                         chop($hash);
2080                                         eval('$fai_classes->'.$hash.'= {}');
2081                                 } else {
2082                                         # A branch, no subrelease
2083                                         if(!exists($fai_classes->{$tmp_release})) {
2084                                                 $fai_classes->{$tmp_release} = {};
2085                                         }
2086                                 }
2087                         }
2088                 }
2090                 # The hash is complete, now we can honor the copy-on-write based missing entries
2091                 foreach my $release (keys %$fai_classes) {
2092                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2093                 }
2094         }
2095         return $result;
2098 sub apply_fai_inheritance {
2099        my $fai_classes = shift || return {};
2100        my $tmp_classes;
2102        # Get the classes from the branch
2103        foreach my $class (keys %{$fai_classes}) {
2104                # Skip subreleases
2105                if($class =~ /^ou=.*$/) {
2106                        next;
2107                } else {
2108                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2109                }
2110        }
2112        # Apply to each subrelease
2113        foreach my $subrelease (keys %{$fai_classes}) {
2114                if($subrelease =~ /ou=/) {
2115                        foreach my $tmp_class (keys %{$tmp_classes}) {
2116                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2117                                        $fai_classes->{$subrelease}->{$tmp_class} =
2118                                        deep_copy($tmp_classes->{$tmp_class});
2119                                } else {
2120                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2121                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2122                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2123                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2124                                                }
2125                                        }
2126                                }
2127                        }
2128                }
2129        }
2131        # Find subreleases in deeper levels
2132        foreach my $subrelease (keys %{$fai_classes}) {
2133                if($subrelease =~ /ou=/) {
2134                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2135                                if($subsubrelease =~ /ou=/) {
2136                                        apply_fai_inheritance($fai_classes->{$subrelease});
2137                                }
2138                        }
2139                }
2140        }
2142        return $fai_classes;
2145 sub get_fai_release_entries {
2146         my $tmp_classes = shift || return;
2147         my $parent = shift || "";
2148         my @result = shift || ();
2150         foreach my $entry (keys %{$tmp_classes}) {
2151                 if(defined($entry)) {
2152                         if($entry =~ /^ou=.*$/) {
2153                                 my $release_name = $entry;
2154                                 $release_name =~ s/ou=//g;
2155                                 if(length($parent)>0) {
2156                                         $release_name = $parent."/".$release_name;
2157                                 }
2158                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2159                                 foreach my $bufentry(@bufentries) {
2160                                         push @result, $bufentry;
2161                                 }
2162                         } else {
2163                                 my @types = get_fai_types($tmp_classes->{$entry});
2164                                 foreach my $type (@types) {
2165                                         push @result, 
2166                                         {
2167                                                 'class' => $entry,
2168                                                 'type' => $type->{'type'},
2169                                                 'release' => $parent,
2170                                                 'state' => $type->{'state'},
2171                                         };
2172                                 }
2173                         }
2174                 }
2175         }
2177         return @result;
2180 sub deep_copy {
2181         my $this = shift;
2182         if (not ref $this) {
2183                 $this;
2184         } elsif (ref $this eq "ARRAY") {
2185                 [map deep_copy($_), @$this];
2186         } elsif (ref $this eq "HASH") {
2187                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2188         } else { die "what type is $_?" }
2192 sub session_run_result {
2193     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2194     $kernel->sig(CHLD => "child_reap");
2197 sub session_run_debug {
2198     my $result = $_[ARG0];
2199     print STDERR "$result\n";
2202 sub session_run_done {
2203     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2204     delete $heap->{task}->{$task_id};
2208 sub create_sources_list {
2209         my $session_id = shift;
2210         my $ldap_handle = &main::get_ldap_handle;
2211         my $result="/tmp/gosa_si_tmp_sources_list";
2213         # Remove old file
2214         if(stat($result)) {
2215                 unlink($result);
2216                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2217         }
2219         my $fh;
2220         open($fh, ">$result");
2221         if (not defined $fh) {
2222                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2223                 return undef;
2224         }
2225         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2226                 my $mesg=$ldap_handle->search(
2227                         base    => $main::ldap_server_dn,
2228                         scope   => 'base',
2229                         attrs   => 'FAIrepository',
2230                         filter  => 'objectClass=FAIrepositoryServer'
2231                 );
2232                 if($mesg->count) {
2233                         foreach my $entry(@{$mesg->{'entries'}}) {
2234                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2235                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2236                                         my $line = "deb $server $release";
2237                                         $sections =~ s/,/ /g;
2238                                         $line.= " $sections";
2239                                         print $fh $line."\n";
2240                                 }
2241                         }
2242                 }
2243         } else {
2244                 if (defined $main::ldap_server_dn){
2245                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2246                 } else {
2247                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2248                 }
2249         }
2250         close($fh);
2252         return $result;
2256 sub run_create_packages_list_db {
2257     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2258         my $session_id = $session->ID;
2260         my $task = POE::Wheel::Run->new(
2261                                         Priority => +20,
2262                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2263                                         StdoutEvent  => "session_run_result",
2264                                         StderrEvent  => "session_run_debug",
2265                                         CloseEvent   => "session_run_done",
2266                                         );
2267         $heap->{task}->{ $task->ID } = $task;
2271 sub create_packages_list_db {
2272         my ($ldap_handle, $sources_file, $session_id) = @_;
2273         
2274         # it should not be possible to trigger a recreation of packages_list_db
2275         # while packages_list_db is under construction, so set flag packages_list_under_construction
2276         # which is tested befor recreation can be started
2277         if (-r $packages_list_under_construction) {
2278                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2279                 return;
2280         } else {
2281                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2282                 # set packages_list_under_construction to true
2283                 system("touch $packages_list_under_construction");
2284                 @packages_list_statements=();
2285         }
2287         if (not defined $session_id) { $session_id = 0; }
2288         if (not defined $ldap_handle) { 
2289                 $ldap_handle= &get_ldap_handle();
2291                 if (not defined $ldap_handle) {
2292                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2293                         unlink($packages_list_under_construction);
2294                         return;
2295                 }
2296         }
2297         if (not defined $sources_file) { 
2298                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2299                 $sources_file = &create_sources_list($session_id);
2300         }
2302         if (not defined $sources_file) {
2303                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2304                 unlink($packages_list_under_construction);
2305                 return;
2306         }
2308         my $line;
2310         open(CONFIG, "<$sources_file") or do {
2311                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2312                 unlink($packages_list_under_construction);
2313                 return;
2314         };
2316         # Read lines
2317         while ($line = <CONFIG>){
2318                 # Unify
2319                 chop($line);
2320                 $line =~ s/^\s+//;
2321                 $line =~ s/^\s+/ /;
2323                 # Strip comments
2324                 $line =~ s/#.*$//g;
2326                 # Skip empty lines
2327                 if ($line =~ /^\s*$/){
2328                         next;
2329                 }
2331                 # Interpret deb line
2332                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2333                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2334                         my $section;
2335                         foreach $section (split(' ', $sections)){
2336                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2337                         }
2338                 }
2339         }
2341         close (CONFIG);
2343         find(\&cleanup_and_extract, keys( %repo_dirs ));
2344         &main::strip_packages_list_statements();
2345         unshift @packages_list_statements, "VACUUM";
2346         $packages_list_db->exec_statementlist(\@packages_list_statements);
2347         unlink($packages_list_under_construction);
2348         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2349         return;
2352 # This function should do some intensive task to minimize the db-traffic
2353 sub strip_packages_list_statements {
2354     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2355         my @new_statement_list=();
2356         my $hash;
2357         my $insert_hash;
2358         my $update_hash;
2359         my $delete_hash;
2360         my $local_timestamp=get_time();
2362         foreach my $existing_entry (@existing_entries) {
2363                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2364         }
2366         foreach my $statement (@packages_list_statements) {
2367                 if($statement =~ /^INSERT/i) {
2368                         # Assign the values from the insert statement
2369                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2370                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2371                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2372                                 # If section or description has changed, update the DB
2373                                 if( 
2374                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2375                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2376                                 ) {
2377                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2378                                 }
2379                         } else {
2380                                 # Insert a non-existing entry to db
2381                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2382                         }
2383                 } elsif ($statement =~ /^UPDATE/i) {
2384                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2385                         /^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;
2386                         foreach my $distribution (keys %{$hash}) {
2387                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2388                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2389                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2390                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2391                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2392                                                 my $section;
2393                                                 my $description;
2394                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2395                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2396                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2397                                                 }
2398                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2399                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2400                                                 }
2401                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2402                                         }
2403                                 }
2404                         }
2405                 }
2406         }
2408         # TODO: Check for orphaned entries
2410         # unroll the insert_hash
2411         foreach my $distribution (keys %{$insert_hash}) {
2412                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2413                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2414                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2415                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2416                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2417                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2418                                 ."'$local_timestamp')";
2419                         }
2420                 }
2421         }
2423         # unroll the update hash
2424         foreach my $distribution (keys %{$update_hash}) {
2425                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2426                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2427                                 my $set = "";
2428                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2429                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2430                                 }
2431                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2432                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2433                                 }
2434                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2435                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2436                                 }
2437                                 if(defined($set) and length($set) > 0) {
2438                                         $set .= "timestamp = '$local_timestamp'";
2439                                 } else {
2440                                         next;
2441                                 }
2442                                 push @new_statement_list, 
2443                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2444                                         ." distribution = '$distribution'"
2445                                         ." AND package = '$package'"
2446                                         ." AND version = '$version'";
2447                         }
2448                 }
2449         }
2451         @packages_list_statements = @new_statement_list;
2455 sub parse_package_info {
2456     my ($baseurl, $dist, $section, $session_id)= @_;
2457     my ($package);
2458     if (not defined $session_id) { $session_id = 0; }
2459     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2460     $repo_dirs{ "${repo_path}/pool" } = 1;
2462     foreach $package ("Packages.gz"){
2463         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2464         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2465         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2466     }
2467     
2471 sub get_package {
2472     my ($url, $dest, $session_id)= @_;
2473     if (not defined $session_id) { $session_id = 0; }
2475     my $tpath = dirname($dest);
2476     -d "$tpath" || mkpath "$tpath";
2478     # This is ugly, but I've no time to take a look at "how it works in perl"
2479     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2480         system("gunzip -cd '$dest' > '$dest.in'");
2481         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2482         unlink($dest);
2483         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2484     } else {
2485         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2486     }
2487     return 0;
2491 sub parse_package {
2492     my ($path, $dist, $srv_path, $session_id)= @_;
2493     if (not defined $session_id) { $session_id = 0;}
2494     my ($package, $version, $section, $description);
2495     my $PACKAGES;
2496     my $timestamp = &get_time();
2498     if(not stat("$path.in")) {
2499         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2500         return;
2501     }
2503     open($PACKAGES, "<$path.in");
2504     if(not defined($PACKAGES)) {
2505         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2506         return;
2507     }
2509     # Read lines
2510     while (<$PACKAGES>){
2511         my $line = $_;
2512         # Unify
2513         chop($line);
2515         # Use empty lines as a trigger
2516         if ($line =~ /^\s*$/){
2517             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2518             push(@packages_list_statements, $sql);
2519             $package = "none";
2520             $version = "none";
2521             $section = "none";
2522             $description = "none"; 
2523             next;
2524         }
2526         # Trigger for package name
2527         if ($line =~ /^Package:\s/){
2528             ($package)= ($line =~ /^Package: (.*)$/);
2529             next;
2530         }
2532         # Trigger for version
2533         if ($line =~ /^Version:\s/){
2534             ($version)= ($line =~ /^Version: (.*)$/);
2535             next;
2536         }
2538         # Trigger for description
2539         if ($line =~ /^Description:\s/){
2540             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2541             next;
2542         }
2544         # Trigger for section
2545         if ($line =~ /^Section:\s/){
2546             ($section)= ($line =~ /^Section: (.*)$/);
2547             next;
2548         }
2550         # Trigger for filename
2551         if ($line =~ /^Filename:\s/){
2552             my ($filename) = ($line =~ /^Filename: (.*)$/);
2553             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2554             next;
2555         }
2556     }
2558     close( $PACKAGES );
2559     unlink( "$path.in" );
2560     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2564 sub store_fileinfo {
2565     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2567     my %fileinfo = (
2568         'package' => $package,
2569         'dist' => $dist,
2570         'version' => $vers,
2571     );
2573     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2577 sub cleanup_and_extract {
2578     my $fileinfo = $repo_files{ $File::Find::name };
2580     if( defined $fileinfo ) {
2582         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2583         my $sql;
2584         my $package = $fileinfo->{ 'package' };
2585         my $newver = $fileinfo->{ 'version' };
2587         mkpath($dir);
2588         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2590                 if( -f "$dir/DEBIAN/templates" ) {
2592                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2594                         my $tmpl= "";
2595                         {
2596                                 local $/=undef;
2597                                 open FILE, "$dir/DEBIAN/templates";
2598                                 $tmpl = &encode_base64(<FILE>);
2599                                 close FILE;
2600                         }
2601                         rmtree("$dir/DEBIAN/templates");
2603                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2604                 push @packages_list_statements, $sql;
2605                 }
2606     }
2608     return;
2612 sub register_at_foreign_servers {   
2613     my ($kernel) = $_[KERNEL];
2615     # hole alle bekannten server aus known_server_db
2616     my $server_sql = "SELECT * FROM $known_server_tn";
2617     my $server_res = $known_server_db->exec_statement($server_sql);
2619     # no entries in known_server_db
2620     if (not ref(@$server_res[0]) eq "ARRAY") { 
2621         # TODO
2622     }
2624     # detect already connected clients
2625     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2626     my $client_res = $known_clients_db->exec_statement($client_sql);
2628     # send my server details to all other gosa-si-server within the network
2629     foreach my $hit (@$server_res) {
2630         my $hostname = @$hit[0];
2631         my $hostkey = &create_passwd;
2633         # add already connected clients to registration message 
2634         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2635         &add_content2xml_hash($myhash, 'key', $hostkey);
2636         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2637         
2638         # build registration message and send it
2639         my $foreign_server_msg = &create_xml_string($myhash);
2640         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2641     }
2642     
2643     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2644     return;
2648 #==== MAIN = main ==============================================================
2649 #  parse commandline options
2650 Getopt::Long::Configure( "bundling" );
2651 GetOptions("h|help" => \&usage,
2652         "c|config=s" => \$cfg_file,
2653         "f|foreground" => \$foreground,
2654         "v|verbose+" => \$verbose,
2655         "no-bus+" => \$no_bus,
2656         "no-arp+" => \$no_arp,
2657            );
2659 #  read and set config parameters
2660 &check_cmdline_param ;
2661 &read_configfile;
2662 &check_pid;
2664 $SIG{CHLD} = 'IGNORE';
2666 # forward error messages to logfile
2667 if( ! $foreground ) {
2668   open( STDIN,  '+>/dev/null' );
2669   open( STDOUT, '+>&STDIN'    );
2670   open( STDERR, '+>&STDIN'    );
2673 # Just fork, if we are not in foreground mode
2674 if( ! $foreground ) { 
2675     chdir '/'                 or die "Can't chdir to /: $!";
2676     $pid = fork;
2677     setsid                    or die "Can't start a new session: $!";
2678     umask 0;
2679 } else { 
2680     $pid = $$; 
2683 # Do something useful - put our PID into the pid_file
2684 if( 0 != $pid ) {
2685     open( LOCK_FILE, ">$pid_file" );
2686     print LOCK_FILE "$pid\n";
2687     close( LOCK_FILE );
2688     if( !$foreground ) { 
2689         exit( 0 ) 
2690     };
2693 # parse head url and revision from svn
2694 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2695 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2696 $server_headURL = defined $1 ? $1 : 'unknown' ;
2697 $server_revision = defined $2 ? $2 : 'unknown' ;
2698 if ($server_headURL =~ /\/tag\// || 
2699         $server_headURL =~ /\/branches\// ) {
2700     $server_status = "stable"; 
2701 } else {
2702     $server_status = "developmental" ;
2706 daemon_log(" ", 1);
2707 daemon_log("$0 started!", 1);
2708 daemon_log("status: $server_status", 1);
2709 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2711 if ($no_bus > 0) {
2712     $bus_activ = "false"
2715 # connect to incoming_db
2716 unlink($incoming_file_name);
2717 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2718 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2720 # connect to gosa-si job queue
2721 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2722 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2724 # connect to known_clients_db
2725 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2726 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2728 # connect to foreign_clients_db
2729 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2730 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2732 # connect to known_server_db
2733 unlink($known_server_file_name);
2734 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2735 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2737 # connect to login_usr_db
2738 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2739 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2741 # connect to fai_server_db and fai_release_db
2742 unlink($fai_server_file_name);
2743 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2744 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2746 unlink($fai_release_file_name);
2747 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2748 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2750 # connect to packages_list_db
2751 #unlink($packages_list_file_name);
2752 unlink($packages_list_under_construction);
2753 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2754 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2756 # connect to messaging_db
2757 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2758 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2761 # create xml object used for en/decrypting
2762 $xml = new XML::Simple();
2765 # foreign servers 
2766 my @foreign_server_list;
2768 # add foreign server from cfg file
2769 if ($foreign_server_string ne "") {
2770     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2771     foreach my $foreign_server (@cfg_foreign_server_list) {
2772         push(@foreign_server_list, $foreign_server);
2773     }
2776 # add foreign server from dns
2777 my @tmp_servers;
2778 if ( !$server_domain) {
2779     # Try our DNS Searchlist
2780     for my $domain(get_dns_domains()) {
2781         chomp($domain);
2782         my @tmp_domains= &get_server_addresses($domain);
2783         if(@tmp_domains) {
2784             for my $tmp_server(@tmp_domains) {
2785                 push @tmp_servers, $tmp_server;
2786             }
2787         }
2788     }
2789     if(@tmp_servers && length(@tmp_servers)==0) {
2790         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2791     }
2792 } else {
2793     @tmp_servers = &get_server_addresses($server_domain);
2794     if( 0 == @tmp_servers ) {
2795         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2796     }
2798 foreach my $server (@tmp_servers) { 
2799     unshift(@foreign_server_list, $server); 
2801 # eliminate duplicate entries
2802 @foreign_server_list = &del_doubles(@foreign_server_list);
2803 my $all_foreign_server = join(", ", @foreign_server_list);
2804 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2806 # add all found foreign servers to known_server
2807 my $act_timestamp = &get_time();
2808 foreach my $foreign_server (@foreign_server_list) {
2809     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2810             primkey=>['hostname'],
2811             hostname=>$foreign_server,
2812             status=>'not_jet_registered',
2813             hostkey=>"none",
2814             timestamp=>$act_timestamp,
2815             } );
2819 POE::Component::Server::TCP->new(
2820         Port => $server_port,
2821         ClientInput => sub {
2822         my ($kernel, $input) = @_[KERNEL, ARG0];
2823         push(@tasks, $input);
2824         push(@msgs_to_decrypt, $input);
2825         $kernel->yield("msg_to_decrypt");
2826         $kernel->yield("next_task");
2827         },
2828     InlineStates => {
2829         next_task => \&next_task,
2830         msg_to_decrypt => \&msg_to_decrypt,
2831         task_result => \&handle_task_result,
2832         task_done   => \&handle_task_done,
2833         task_debug  => \&handle_task_debug,
2834         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2835     }
2836 );
2838 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2840 # create session for repeatedly checking the job queue for jobs
2841 POE::Session->create(
2842         inline_states => {
2843                 _start => \&_start,
2844         register_at_foreign_servers => \&register_at_foreign_servers,
2845                 sig_handler => \&sig_handler,
2846         watch_for_new_messages => \&watch_for_new_messages,
2847         watch_for_delivery_messages => \&watch_for_delivery_messages,
2848         watch_for_done_messages => \&watch_for_done_messages,
2849                 watch_for_new_jobs => \&watch_for_new_jobs,
2850         watch_for_done_jobs => \&watch_for_done_jobs,
2851         watch_for_old_known_clients => \&watch_for_old_known_clients,
2852         create_packages_list_db => \&run_create_packages_list_db,
2853         create_fai_server_db => \&run_create_fai_server_db,
2854         create_fai_release_db => \&run_create_fai_release_db,
2855         session_run_result => \&session_run_result,
2856         session_run_debug => \&session_run_debug,
2857         session_run_done => \&session_run_done,
2858         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2859         }
2860 );
2863 # import all modules
2864 &import_modules;
2866 # TODO
2867 # check wether all modules are gosa-si valid passwd check
2871 POE::Kernel->run();
2872 exit;