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 $timestamp = &get_time();
895     my $new_status;
896     my $act_status;
897     my ($sql_statement, $res);
898   
899     if( $msg_header ) {
900         $header = "'$msg_header'-";
901     } else {
902         $header = "";
903     }
905         # Patch the source ip
906         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
907                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
908                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
909         }
911     # encrypt xml msg
912     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
914     # opensocket
915     my $socket = &open_socket($address);
916     if( !$socket ) {
917         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
918         $error++;
919     }
920     
921     if( $error == 0 ) {
922         # send xml msg
923         print $socket $crypted_msg."\n";
925         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
926         daemon_log("DEBUG: message:\n$msg", 9);
927         
928     }
930     # close socket in any case
931     if( $socket ) {
932         close $socket;
933     }
935     if( $error > 0 ) { $new_status = "down"; }
936     else { $new_status = $msg_header; }
939     # known_clients
940     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
941     $res = $known_clients_db->select_dbentry($sql_statement);
942     if( keys(%$res) > 0) {
943         $act_status = $res->{1}->{'status'};
944         if ($act_status eq "down" && $new_status eq "down") {
945             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
946             $res = $known_clients_db->del_dbentry($sql_statement);
947             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
948         } else { 
949             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
950             $res = $known_clients_db->update_dbentry($sql_statement);
951             if($new_status eq "down"){
952                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
953             } else {
954                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
955             }
956         }
957     }
959     # known_server
960     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
961     $res = $known_server_db->select_dbentry($sql_statement);
962     if( keys(%$res) > 0 ) {
963         $act_status = $res->{1}->{'status'};
964         if ($act_status eq "down" && $new_status eq "down") {
965             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
966             $res = $known_server_db->del_dbentry($sql_statement);
967             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
968         } 
969         else { 
970             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
971             $res = $known_server_db->update_dbentry($sql_statement);
972             if($new_status eq "down"){
973                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
974             }
975             else {
976                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
977             }
978         }
979     }
980     return $error; 
984 sub update_jobdb_status_for_send_msgs {
985     my ($answer, $error) = @_;
986     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
987         my $jobdb_id = $1;
988             
989         # sending msg faild
990         if( $error ) {
991             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
992                 my $sql_statement = "UPDATE $job_queue_tn ".
993                     "SET status='error', result='can not deliver msg, please consult log file' ".
994                     "WHERE id=$jobdb_id";
995                 my $res = $job_db->update_dbentry($sql_statement);
996             }
998         # sending msg was successful
999         } else {
1000             my $sql_statement = "UPDATE $job_queue_tn ".
1001                 "SET status='done' ".
1002                 "WHERE id=$jobdb_id AND status='processed'";
1003             my $res = $job_db->update_dbentry($sql_statement);
1004         }
1005     }
1008 sub _start {
1009     my ($kernel) = $_[KERNEL];
1010     &trigger_db_loop($kernel);
1011     $global_kernel = $kernel;
1012     $kernel->yield('register_at_foreign_servers');
1013         $kernel->yield('create_fai_server_db', $fai_server_tn );
1014         $kernel->yield('create_fai_release_db', $fai_release_tn );
1015         $kernel->sig(USR1 => "sig_handler");
1016         $kernel->sig(USR2 => "create_packages_list_db");
1019 sub sig_handler {
1020         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1021         daemon_log("0 INFO got signal '$signal'", 1); 
1022         $kernel->sig_handled();
1023         return;
1027 sub msg_to_decrypt {
1028     my ($session, $heap) = @_[SESSION, HEAP];
1029     my $session_id = $session->ID;
1030     my ($msg, $msg_hash, $module);
1031     my $error = 0;
1033     # hole neue msg aus @msgs_to_decrypt
1034     my $next_msg = shift @msgs_to_decrypt;
1035     
1036     # entschlüssle sie
1038     # msg is from a new client or gosa
1039     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1040     # msg is from a gosa-si-server or gosa-si-bus
1041     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1043     }
1044     # msg is from a gosa-si-client
1045     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1046         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1047     }
1048     # an error occurred
1049     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1050         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1051         # could not understand a msg from its server the client cause a re-registering process
1052         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);
1053         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1054         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1055         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1056             my $host_name = $hit->{'hostname'};
1057             my $host_key = $hit->{'hostkey'};
1058             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1059             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1060             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1061         }
1062         $error++;
1063     }
1064     
1065     # add message to incoming_db
1066     if( $error == 0) {
1067         my $header = @{$msg_hash->{'header'}}[0];
1068         my $target = @{$msg_hash->{'target'}}[0];
1069         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1070                 primkey=>[],
1071                 headertag=>$header,
1072                                 targettag=>$target,
1073                 xmlmessage=>$msg,
1074                 timestamp=>&get_time,
1075                 module=>$module,
1076                 } );
1077         if ($res != 0) {
1078                         # TODO ist das mit $! so ok???
1079             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1080         }
1081     }
1086 sub next_task {
1087     my ($session, $heap) = @_[SESSION, HEAP];
1088     my $task = POE::Wheel::Run->new(
1089             Program => sub { process_task($session, $heap) },
1090             StdioFilter => POE::Filter::Reference->new(),
1091             StdoutEvent  => "task_result",
1092             StderrEvent  => "task_debug",
1093             CloseEvent   => "task_done",
1094             );
1096     $heap->{task}->{ $task->ID } = $task;
1099 sub handle_task_result {
1100     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1101     my $client_answer = $result->{'answer'};
1102     if( $client_answer =~ s/session_id=(\d+)$// ) {
1103         my $session_id = $1;
1104         if( defined $session_id ) {
1105             my $session_reference = $kernel->ID_id_to_session($session_id);
1106             if( defined $session_reference ) {
1107                 $heap = $session_reference->get_heap();
1108             }
1109         }
1111         if(exists $heap->{'client'}) {
1112             $heap->{'client'}->put($client_answer);
1113         }
1114     }
1115     $kernel->sig(CHLD => "child_reap");
1118 sub handle_task_debug {
1119     my $result = $_[ARG0];
1120     print STDERR "$result\n";
1123 sub handle_task_done {
1124     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1125     delete $heap->{task}->{$task_id};
1128 sub process_task {
1129     no strict "refs";
1130     my ($session, $heap, $input) = @_;
1131     my $session_id = $session->ID;
1132     my $error = 0;
1133     my $answer_l;
1134     my ($answer_header, @answer_target_l, $answer_source);
1135     my $client_answer = "";
1137         ##################################################
1138         # fetch first unprocessed message from incoming_db
1139     # sometimes the program is faster than sqlite, so wait until informations are present at db
1140     my $id_sql;
1141     my $id_res;
1142     my $message_id;
1143 # TODO : das hier ist sehr sehr unschön        
1144 # to be tested: speed enhancement with usleep 100000???
1145     while (1) {
1146         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1147         $id_res = $incoming_db->exec_statement($id_sql);
1148         $message_id = @{@$id_res[0]}[0];
1149         if (defined $message_id) { last }
1150         usleep(100000);
1151     }
1153     # fetch new message from incoming_db
1154     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1155     my $res = $incoming_db->exec_statement($sql);
1157     # prepare all variables needed to process message
1158     my $msg = @{@$res[0]}[4];
1159     my $incoming_id = @{@$res[0]}[0];
1160     my $module = @{@$res[0]}[5];
1161     my $header =  @{@$res[0]}[2];
1162     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1164     # messages which are an answer to a still running process should not be processed here
1165     if ($header =~ /^answer_(\d+)/) {
1166         return;
1167     }
1168    
1169     # delete message from db 
1170     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1171     my $delete_res = $incoming_db->exec_statement($delete_sql);
1173     ######################
1174     # process incoming msg
1175     if( $error == 0) {
1176         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1177                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1178         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1179         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1181         if ( 0 < @{$answer_l} ) {
1182             my $answer_str = join("\n", @{$answer_l});
1183             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1184                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1185             }
1186             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1187         } else {
1188             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1189         }
1191     }
1192     if( !$answer_l ) { $error++ };
1194     ########
1195     # answer
1196     if( $error == 0 ) {
1198         foreach my $answer ( @{$answer_l} ) {
1199             # check outgoing msg to xml validity
1200             my $answer_hash = &check_outgoing_xml_validity($answer);
1201             if( not defined $answer_hash ) { next; }
1202             
1203             $answer_header = @{$answer_hash->{'header'}}[0];
1204             @answer_target_l = @{$answer_hash->{'target'}};
1205             $answer_source = @{$answer_hash->{'source'}}[0];
1207             # deliver msg to all targets 
1208             foreach my $answer_target ( @answer_target_l ) {
1210                 # targets of msg are all gosa-si-clients in known_clients_db
1211                 if( $answer_target eq "*" ) {
1212                     # answer is for all clients
1213                     my $sql_statement= "SELECT * FROM known_clients";
1214                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1215                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1216                         my $host_name = $hit->{hostname};
1217                         my $host_key = $hit->{hostkey};
1218                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1219                         &update_jobdb_status_for_send_msgs($answer, $error);
1220                     }
1221                 }
1223                 # targets of msg are all gosa-si-server in known_server_db
1224                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1225                     # answer is for all server in known_server
1226                     my $sql_statement= "SELECT * FROM known_server";
1227                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1228                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1229                         my $host_name = $hit->{hostname};
1230                         my $host_key = $hit->{hostkey};
1231                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1232                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1233                         &update_jobdb_status_for_send_msgs($answer, $error);
1234                     }
1235                 }
1237                 # target of msg is GOsa
1238                                 elsif( $answer_target eq "GOSA" ) {
1239                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1240                                         my $add_on = "";
1241                     if( defined $session_id ) {
1242                         $add_on = ".session_id=$session_id";
1243                     }
1244                     # answer is for GOSA and has to returned to connected client
1245                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1246                     $client_answer = $gosa_answer.$add_on;
1247                 }
1249                 # target of msg is job queue at this host
1250                 elsif( $answer_target eq "JOBDB") {
1251                     $answer =~ /<header>(\S+)<\/header>/;   
1252                     my $header;
1253                     if( defined $1 ) { $header = $1; }
1254                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1255                     &update_jobdb_status_for_send_msgs($answer, $error);
1256                 }
1258                 # target of msg is a mac address
1259                 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 ) {
1260                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1261                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1262                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1263                     my $found_ip_flag = 0;
1264                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1265                         my $host_name = $hit->{hostname};
1266                         my $host_key = $hit->{hostkey};
1267                         $answer =~ s/$answer_target/$host_name/g;
1268                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1269                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1270                         &update_jobdb_status_for_send_msgs($answer, $error);
1271                         $found_ip_flag++ ;
1272                     }   
1273                     if( $found_ip_flag == 0) {
1274                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1275                         if( $bus_activ eq "true" ) { 
1276                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1277                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1278                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1279                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1280                                 my $bus_address = $hit->{hostname};
1281                                 my $bus_key = $hit->{hostkey};
1282                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1283                                 &update_jobdb_status_for_send_msgs($answer, $error);
1284                                 last;
1285                             }
1286                         }
1288                     }
1290                 #  answer is for one specific host   
1291                 } else {
1292                     # get encrypt_key
1293                     my $encrypt_key = &get_encrypt_key($answer_target);
1294                     if( not defined $encrypt_key ) {
1295                         # unknown target, forward msg to bus
1296                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1297                         if( $bus_activ eq "true" ) { 
1298                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1299                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1300                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1301                             my $res_length = keys( %{$query_res} );
1302                             if( $res_length == 0 ){
1303                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1304                                         "no bus found in known_server", 3);
1305                             }
1306                             else {
1307                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1308                                     my $bus_key = $hit->{hostkey};
1309                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1310                                     &update_jobdb_status_for_send_msgs($answer, $error);
1311                                 }
1312                             }
1313                         }
1314                         next;
1315                     }
1316                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1317                     &update_jobdb_status_for_send_msgs($answer, $error);
1318                 }
1319             }
1320         }
1321     }
1323     my $filter = POE::Filter::Reference->new();
1324     my %result = ( 
1325             status => "seems ok to me",
1326             answer => $client_answer,
1327             );
1329     my $output = $filter->put( [ \%result ] );
1330     print @$output;
1336 sub trigger_db_loop {
1337         my ($kernel) = @_ ;
1338         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1339         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1340         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1341     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1342         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1343     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1347 sub watch_for_done_jobs {
1348     my ($kernel,$heap) = @_[KERNEL, HEAP];
1350     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1351         " WHERE status='done'";
1352         my $res = $job_db->select_dbentry( $sql_statement );
1354     while( my ($id, $hit) = each %{$res} ) {
1355         my $jobdb_id = $hit->{id};
1356         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1357         my $res = $job_db->del_dbentry($sql_statement); 
1358     }
1360     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1364 sub watch_for_new_jobs {
1365         if($watch_for_new_jobs_in_progress == 0) {
1366                 $watch_for_new_jobs_in_progress = 1;
1367                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1369                 # check gosa job queue for jobs with executable timestamp
1370                 my $timestamp = &get_time();
1371                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1372                 my $res = $job_db->exec_statement( $sql_statement );
1374                 # Merge all new jobs that would do the same actions
1375                 my @drops;
1376                 my $hits;
1377                 foreach my $hit (reverse @{$res} ) {
1378                         my $macaddress= lc @{$hit}[8];
1379                         my $headertag= @{$hit}[5];
1380                         if(
1381                                 defined($hits->{$macaddress}) &&
1382                                 defined($hits->{$macaddress}->{$headertag}) &&
1383                                 defined($hits->{$macaddress}->{$headertag}[0])
1384                         ) {
1385                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1386                         }
1387                         $hits->{$macaddress}->{$headertag}= $hit;
1388                 }
1390                 # Delete new jobs with a matching job in state 'processing'
1391                 foreach my $macaddress (keys %{$hits}) {
1392                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1393                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1394                                 if(defined($jobdb_id)) {
1395                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1396                                         my $res = $job_db->exec_statement( $sql_statement );
1397                                         foreach my $hit (@{$res}) {
1398                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1399                                         }
1400                                 } else {
1401                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1402                                 }
1403                         }
1404                 }
1406                 # Commit deletion
1407                 $job_db->exec_statementlist(\@drops);
1409                 # Look for new jobs that could be executed
1410                 foreach my $macaddress (keys %{$hits}) {
1412                         # Look if there is an executing job
1413                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1414                         my $res = $job_db->exec_statement( $sql_statement );
1416                         # Skip new jobs for host if there is a processing job
1417                         if(defined($res) and defined @{$res}[0]) {
1418                                 next;
1419                         }
1421                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1422                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1423                                 if(defined($jobdb_id)) {
1424                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1426                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1427                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1428                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1430                                         # expect macaddress is unique!!!!!!
1431                                         my $target = $res_hash->{1}->{hostname};
1433                                         # change header
1434                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1436                                         # add sqlite_id
1437                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1439                                         $job_msg =~ /<header>(\S+)<\/header>/;
1440                                         my $header = $1 ;
1441                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1443                                         # update status in job queue to 'processing'
1444                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1445                                         my $res = $job_db->update_dbentry($sql_statement);
1446 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1448                                         # We don't want parallel processing
1449                                         last;
1450                                 }
1451                         }
1452                 }
1454                 $watch_for_new_jobs_in_progress = 0;
1455                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1456         }
1460 sub watch_for_new_messages {
1461     my ($kernel,$heap) = @_[KERNEL, HEAP];
1462     my @coll_user_msg;   # collection list of outgoing messages
1463     
1464     # check messaging_db for new incoming messages with executable timestamp
1465     my $timestamp = &get_time();
1466     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1467     my $res = $messaging_db->exec_statement( $sql_statement );
1468         foreach my $hit (@{$res}) {
1470         # create outgoing messages
1471         my $message_to = @{$hit}[3];
1472         # translate message_to to plain login name
1473         my @message_to_l = split(/,/, $message_to);  
1474                 my %receiver_h; 
1475                 foreach my $receiver (@message_to_l) {
1476                         if ($receiver =~ /^u_([\s\S]*)$/) {
1477                                 $receiver_h{$1} = 0;
1478                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1479                                 my $group_name = $1;
1480                                 # fetch all group members from ldap and add them to receiver hash
1481                                 my $ldap_handle = &get_ldap_handle();
1482                                 if (defined $ldap_handle) {
1483                                                 my $mesg = $ldap_handle->search(
1484                                                                                 base => $ldap_base,
1485                                                                                 scope => 'sub',
1486                                                                                 attrs => ['memberUid'],
1487                                                                                 filter => "cn=$group_name",
1488                                                                                 );
1489                                                 if ($mesg->count) {
1490                                                                 my @entries = $mesg->entries;
1491                                                                 foreach my $entry (@entries) {
1492                                                                                 my @receivers= $entry->get_value("memberUid");
1493                                                                                 foreach my $receiver (@receivers) { 
1494                                                                                                 $receiver_h{$1} = 0;
1495                                                                                 }
1496                                                                 }
1497                                                 } 
1498                                                 # translating errors ?
1499                                                 if ($mesg->code) {
1500                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1501                                                 }
1502                                 # ldap handle error ?           
1503                                 } else {
1504                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1505                                 }
1506                         } else {
1507                                 my $sbjct = &encode_base64(@{$hit}[1]);
1508                                 my $msg = &encode_base64(@{$hit}[7]);
1509                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1510                         }
1511                 }
1512                 my @receiver_l = keys(%receiver_h);
1514         my $message_id = @{$hit}[0];
1516         #add each outgoing msg to messaging_db
1517         my $receiver;
1518         foreach $receiver (@receiver_l) {
1519             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1520                 "VALUES ('".
1521                 $message_id."', '".    # id
1522                 @{$hit}[1]."', '".     # subject
1523                 @{$hit}[2]."', '".     # message_from
1524                 $receiver."', '".      # message_to
1525                 "none"."', '".         # flag
1526                 "out"."', '".          # direction
1527                 @{$hit}[6]."', '".     # delivery_time
1528                 @{$hit}[7]."', '".     # message
1529                 $timestamp."'".     # timestamp
1530                 ")";
1531             &daemon_log("M DEBUG: $sql_statement", 1);
1532             my $res = $messaging_db->exec_statement($sql_statement);
1533             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1534         }
1536         # set incoming message to flag d=deliverd
1537         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1538         &daemon_log("M DEBUG: $sql_statement", 7);
1539         $res = $messaging_db->update_dbentry($sql_statement);
1540         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1541     }
1543     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1544     return;
1547 sub watch_for_delivery_messages {
1548     my ($kernel, $heap) = @_[KERNEL, HEAP];
1550     # select outgoing messages
1551     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1552     #&daemon_log("0 DEBUG: $sql", 7);
1553     my $res = $messaging_db->exec_statement( $sql_statement );
1554     
1555     # build out msg for each    usr
1556     foreach my $hit (@{$res}) {
1557         my $receiver = @{$hit}[3];
1558         my $msg_id = @{$hit}[0];
1559         my $subject = @{$hit}[1];
1560         my $message = @{$hit}[7];
1562         # resolve usr -> host where usr is logged in
1563         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1564         #&daemon_log("0 DEBUG: $sql", 7);
1565         my $res = $login_users_db->exec_statement($sql);
1567         # reciver is logged in nowhere
1568         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1570                 my $send_succeed = 0;
1571                 foreach my $hit (@$res) {
1572                                 my $receiver_host = @$hit[0];
1573                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1575                                 # fetch key to encrypt msg propperly for usr/host
1576                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1577                                 &daemon_log("0 DEBUG: $sql", 7);
1578                                 my $res = $known_clients_db->select_dbentry($sql);
1580                                 # host is already down
1581                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1583                                 # host is on
1584                                 my $receiver_key = @{@{$res}[0]}[2];
1585                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1586                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1587                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1588                                 if ($error == 0 ) {
1589                                         $send_succeed++ ;
1590                                 }
1591                 }
1593                 if ($send_succeed) {
1594                                 # set outgoing msg at db to deliverd
1595                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1596                                 &daemon_log("0 DEBUG: $sql", 7);
1597                                 my $res = $messaging_db->exec_statement($sql); 
1598                 }
1599         }
1601     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1602     return;
1606 sub watch_for_done_messages {
1607     my ($kernel,$heap) = @_[KERNEL, HEAP];
1609     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1610     #&daemon_log("0 DEBUG: $sql", 7);
1611     my $res = $messaging_db->exec_statement($sql); 
1613     foreach my $hit (@{$res}) {
1614         my $msg_id = @{$hit}[0];
1616         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1617         #&daemon_log("0 DEBUG: $sql", 7); 
1618         my $res = $messaging_db->exec_statement($sql);
1620         # not all usr msgs have been seen till now
1621         if ( ref(@$res[0]) eq "ARRAY") { next; }
1622         
1623         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1624         #&daemon_log("0 DEBUG: $sql", 7);
1625         $res = $messaging_db->exec_statement($sql);
1626     
1627     }
1629     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1630     return;
1634 sub watch_for_old_known_clients {
1635     my ($kernel,$heap) = @_[KERNEL, HEAP];
1637     my $sql_statement = "SELECT * FROM $known_clients_tn";
1638     my $res = $known_clients_db->select_dbentry( $sql_statement );
1640     my $act_time = int(&get_time());
1641     while ( my ($hit_num, $hit) = each %$res) {
1642         my $expired_timestamp = int($hit->{'timestamp'}) + (2 * int($hit->{'keylifetime'}));
1643         if ($act_time > $expired_timestamp) {
1644             my $hostname = $hit->{'hostname'};
1645             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1646             my $del_res = $known_clients_db->exec_statement($del_sql);
1648             &main::daemon_log("0 INFO: timestamp of client '$hostname' is expired, client will be deleted from known_clients_db", 5);
1649         }
1651     }
1653     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1657 sub get_ldap_handle {
1658         my ($session_id) = @_;
1659         my $heap;
1660         my $ldap_handle;
1662         if (not defined $session_id ) { $session_id = 0 };
1663         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1665         if ($session_id == 0) {
1666                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1667                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1668                 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password); 
1670         } else {
1671                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1672                 if( defined $session_reference ) {
1673                         $heap = $session_reference->get_heap();
1674                 }
1676                 if (not defined $heap) {
1677                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1678                         return;
1679                 }
1681                 # TODO: This "if" is nonsense, because it doesn't prove that the
1682                 #       used handle is still valid - or if we've to reconnect...
1683                 #if (not exists $heap->{ldap_handle}) {
1684                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1685                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1686                         $heap->{ldap_handle} = $ldap_handle;
1687                 #}
1688         }
1689         return $ldap_handle;
1693 sub change_fai_state {
1694     my ($st, $targets, $session_id) = @_;
1695     $session_id = 0 if not defined $session_id;
1696     # Set FAI state to localboot
1697     my %mapActions= (
1698         reboot    => '',
1699         update    => 'softupdate',
1700         localboot => 'localboot',
1701         reinstall => 'install',
1702         rescan    => '',
1703         wake      => '',
1704         memcheck  => 'memcheck',
1705         sysinfo   => 'sysinfo',
1706         install   => 'install',
1707     );
1709     # Return if this is unknown
1710     if (!exists $mapActions{ $st }){
1711         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1712       return;
1713     }
1715     my $state= $mapActions{ $st };
1717     my $ldap_handle = &get_ldap_handle($session_id);
1718     if( defined($ldap_handle) ) {
1720       # Build search filter for hosts
1721         my $search= "(&(objectClass=GOhard)";
1722         foreach (@{$targets}){
1723             $search.= "(macAddress=$_)";
1724         }
1725         $search.= ")";
1727       # If there's any host inside of the search string, procress them
1728         if (!($search =~ /macAddress/)){
1729             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1730             return;
1731         }
1733       # Perform search for Unit Tag
1734       my $mesg = $ldap_handle->search(
1735           base   => $ldap_base,
1736           scope  => 'sub',
1737           attrs  => ['dn', 'FAIstate', 'objectClass'],
1738           filter => "$search"
1739           );
1741           if ($mesg->count) {
1742                   my @entries = $mesg->entries;
1743                   foreach my $entry (@entries) {
1744                           # Only modify entry if it is not set to '$state'
1745                           if ($entry->get_value("FAIstate") ne "$state"){
1746                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1747                                   my $result;
1748                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1749                                   if (exists $tmp{'FAIobject'}){
1750                                           if ($state eq ''){
1751                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1752                                                           delete => [ FAIstate => [] ] ]);
1753                                           } else {
1754                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1755                                                           replace => [ FAIstate => $state ] ]);
1756                                           }
1757                                   } elsif ($state ne ''){
1758                                           $result= $ldap_handle->modify($entry->dn, changes => [
1759                                                   add     => [ objectClass => 'FAIobject' ],
1760                                                   add     => [ FAIstate => $state ] ]);
1761                                   }
1763                                   # Errors?
1764                                   if ($result->code){
1765                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1766                                   }
1767                           } else {
1768                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1769                           }  
1770                   }
1771           }
1772     # if no ldap handle defined
1773     } else {
1774         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1775     }
1780 sub change_goto_state {
1781     my ($st, $targets, $session_id) = @_;
1782     $session_id = 0  if not defined $session_id;
1784     # Switch on or off?
1785     my $state= $st eq 'active' ? 'active': 'locked';
1787     my $ldap_handle = &get_ldap_handle($session_id);
1788     if( defined($ldap_handle) ) {
1790       # Build search filter for hosts
1791       my $search= "(&(objectClass=GOhard)";
1792       foreach (@{$targets}){
1793         $search.= "(macAddress=$_)";
1794       }
1795       $search.= ")";
1797       # If there's any host inside of the search string, procress them
1798       if (!($search =~ /macAddress/)){
1799         return;
1800       }
1802       # Perform search for Unit Tag
1803       my $mesg = $ldap_handle->search(
1804           base   => $ldap_base,
1805           scope  => 'sub',
1806           attrs  => ['dn', 'gotoMode'],
1807           filter => "$search"
1808           );
1810       if ($mesg->count) {
1811         my @entries = $mesg->entries;
1812         foreach my $entry (@entries) {
1814           # Only modify entry if it is not set to '$state'
1815           if ($entry->get_value("gotoMode") ne $state){
1817             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1818             my $result;
1819             $result= $ldap_handle->modify($entry->dn, changes => [
1820                                                 replace => [ gotoMode => $state ] ]);
1822             # Errors?
1823             if ($result->code){
1824               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1825             }
1827           }
1828         }
1829       }
1831     }
1835 sub run_create_fai_server_db {
1836     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1837     my $session_id = $session->ID;
1838     my $task = POE::Wheel::Run->new(
1839             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1840             StdoutEvent  => "session_run_result",
1841             StderrEvent  => "session_run_debug",
1842             CloseEvent   => "session_run_done",
1843             );
1845     $heap->{task}->{ $task->ID } = $task;
1846     return;
1850 sub create_fai_server_db {
1851     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1852         my $result;
1854         if (not defined $session_id) { $session_id = 0; }
1855     my $ldap_handle = &get_ldap_handle();
1856         if(defined($ldap_handle)) {
1857                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1858                 my $mesg= $ldap_handle->search(
1859                         base   => $ldap_base,
1860                         scope  => 'sub',
1861                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1862                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1863                 );
1864                 if($mesg->{'resultCode'} == 0 &&
1865                    $mesg->count != 0) {
1866                    foreach my $entry (@{$mesg->{entries}}) {
1867                            if($entry->exists('FAIrepository')) {
1868                                    # Add an entry for each Repository configured for server
1869                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1870                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1871                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1872                                                    $result= $fai_server_db->add_dbentry( { 
1873                                                                    table => $table_name,
1874                                                                    primkey => ['server', 'release', 'tag'],
1875                                                                    server => $tmp_url,
1876                                                                    release => $tmp_release,
1877                                                                    sections => $tmp_sections,
1878                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1879                                                            } );
1880                                            }
1881                                    }
1882                            }
1883                    }
1884                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1886                 # TODO: Find a way to post the 'create_packages_list_db' event
1887                 if(not defined($dont_create_packages_list)) {
1888                         &create_packages_list_db(undef, undef, $session_id);
1889                 }
1890         }       
1891     
1892     $ldap_handle->disconnect;
1893         return $result;
1897 sub run_create_fai_release_db {
1898     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1899         my $session_id = $session->ID;
1900     my $task = POE::Wheel::Run->new(
1901             Program => sub { &create_fai_release_db($table_name, $session_id) },
1902             StdoutEvent  => "session_run_result",
1903             StderrEvent  => "session_run_debug",
1904             CloseEvent   => "session_run_done",
1905             );
1907     $heap->{task}->{ $task->ID } = $task;
1908     return;
1912 sub create_fai_release_db {
1913         my ($table_name, $session_id) = @_;
1914         my $result;
1916     # used for logging
1917     if (not defined $session_id) { $session_id = 0; }
1919     my $ldap_handle = &get_ldap_handle();
1920         if(defined($ldap_handle)) {
1921                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1922                 my $mesg= $ldap_handle->search(
1923                         base   => $ldap_base,
1924                         scope  => 'sub',
1925                         attrs  => [],
1926                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1927                 );
1928                 if($mesg->{'resultCode'} == 0 &&
1929                         $mesg->count != 0) {
1930                         # Walk through all possible FAI container ou's
1931                         my @sql_list;
1932                         my $timestamp= &get_time();
1933                         foreach my $ou (@{$mesg->{entries}}) {
1934                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1935                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1936                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1937                                         if(@tmp_array) {
1938                                                 foreach my $entry (@tmp_array) {
1939                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1940                                                                 my $sql= 
1941                                                                 "INSERT INTO $table_name "
1942                                                                 ."(timestamp, release, class, type, state) VALUES ("
1943                                                                 .$timestamp.","
1944                                                                 ."'".$entry->{'release'}."',"
1945                                                                 ."'".$entry->{'class'}."',"
1946                                                                 ."'".$entry->{'type'}."',"
1947                                                                 ."'".$entry->{'state'}."')";
1948                                                                 push @sql_list, $sql;
1949                                                         }
1950                                                 }
1951                                         }
1952                                 }
1953                         }
1955                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1956                         if(@sql_list) {
1957                                 unshift @sql_list, "VACUUM";
1958                                 unshift @sql_list, "DELETE FROM $table_name";
1959                                 $fai_release_db->exec_statementlist(\@sql_list);
1960                         }
1961                         daemon_log("$session_id DEBUG: Done with inserting",7);
1962                 }
1963                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1964         }
1965     $ldap_handle->disconnect;
1966         return $result;
1969 sub get_fai_types {
1970         my $tmp_classes = shift || return undef;
1971         my @result;
1973         foreach my $type(keys %{$tmp_classes}) {
1974                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1975                         my $entry = {
1976                                 type => $type,
1977                                 state => $tmp_classes->{$type}[0],
1978                         };
1979                         push @result, $entry;
1980                 }
1981         }
1983         return @result;
1986 sub get_fai_state {
1987         my $result = "";
1988         my $tmp_classes = shift || return $result;
1990         foreach my $type(keys %{$tmp_classes}) {
1991                 if(defined($tmp_classes->{$type}[0])) {
1992                         $result = $tmp_classes->{$type}[0];
1993                         
1994                 # State is equal for all types in class
1995                         last;
1996                 }
1997         }
1999         return $result;
2002 sub resolve_fai_classes {
2003         my ($fai_base, $ldap_handle, $session_id) = @_;
2004         if (not defined $session_id) { $session_id = 0; }
2005         my $result;
2006         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2007         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2008         my $fai_classes;
2010         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2011         my $mesg= $ldap_handle->search(
2012                 base   => $fai_base,
2013                 scope  => 'sub',
2014                 attrs  => ['cn','objectClass','FAIstate'],
2015                 filter => $fai_filter,
2016         );
2017         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2019         if($mesg->{'resultCode'} == 0 &&
2020                 $mesg->count != 0) {
2021                 foreach my $entry (@{$mesg->{entries}}) {
2022                         if($entry->exists('cn')) {
2023                                 my $tmp_dn= $entry->dn();
2025                                 # Skip classname and ou dn parts for class
2026                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2028                                 # Skip classes without releases
2029                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2030                                         next;
2031                                 }
2033                                 my $tmp_cn= $entry->get_value('cn');
2034                                 my $tmp_state= $entry->get_value('FAIstate');
2036                                 my $tmp_type;
2037                                 # Get FAI type
2038                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2039                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2040                                                 $tmp_type= $oclass;
2041                                                 last;
2042                                         }
2043                                 }
2045                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2046                                         # A Subrelease
2047                                         my @sub_releases = split(/,/, $tmp_release);
2049                                         # Walk through subreleases and build hash tree
2050                                         my $hash;
2051                                         while(my $tmp_sub_release = pop @sub_releases) {
2052                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2053                                         }
2054                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2055                                 } else {
2056                                         # A branch, no subrelease
2057                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2058                                 }
2059                         } elsif (!$entry->exists('cn')) {
2060                                 my $tmp_dn= $entry->dn();
2061                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2063                                 # Skip classes without releases
2064                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2065                                         next;
2066                                 }
2068                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2069                                         # A Subrelease
2070                                         my @sub_releases= split(/,/, $tmp_release);
2072                                         # Walk through subreleases and build hash tree
2073                                         my $hash;
2074                                         while(my $tmp_sub_release = pop @sub_releases) {
2075                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2076                                         }
2077                                         # Remove the last two characters
2078                                         chop($hash);
2079                                         chop($hash);
2081                                         eval('$fai_classes->'.$hash.'= {}');
2082                                 } else {
2083                                         # A branch, no subrelease
2084                                         if(!exists($fai_classes->{$tmp_release})) {
2085                                                 $fai_classes->{$tmp_release} = {};
2086                                         }
2087                                 }
2088                         }
2089                 }
2091                 # The hash is complete, now we can honor the copy-on-write based missing entries
2092                 foreach my $release (keys %$fai_classes) {
2093                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2094                 }
2095         }
2096         return $result;
2099 sub apply_fai_inheritance {
2100        my $fai_classes = shift || return {};
2101        my $tmp_classes;
2103        # Get the classes from the branch
2104        foreach my $class (keys %{$fai_classes}) {
2105                # Skip subreleases
2106                if($class =~ /^ou=.*$/) {
2107                        next;
2108                } else {
2109                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2110                }
2111        }
2113        # Apply to each subrelease
2114        foreach my $subrelease (keys %{$fai_classes}) {
2115                if($subrelease =~ /ou=/) {
2116                        foreach my $tmp_class (keys %{$tmp_classes}) {
2117                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2118                                        $fai_classes->{$subrelease}->{$tmp_class} =
2119                                        deep_copy($tmp_classes->{$tmp_class});
2120                                } else {
2121                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2122                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2123                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2124                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2125                                                }
2126                                        }
2127                                }
2128                        }
2129                }
2130        }
2132        # Find subreleases in deeper levels
2133        foreach my $subrelease (keys %{$fai_classes}) {
2134                if($subrelease =~ /ou=/) {
2135                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2136                                if($subsubrelease =~ /ou=/) {
2137                                        apply_fai_inheritance($fai_classes->{$subrelease});
2138                                }
2139                        }
2140                }
2141        }
2143        return $fai_classes;
2146 sub get_fai_release_entries {
2147         my $tmp_classes = shift || return;
2148         my $parent = shift || "";
2149         my @result = shift || ();
2151         foreach my $entry (keys %{$tmp_classes}) {
2152                 if(defined($entry)) {
2153                         if($entry =~ /^ou=.*$/) {
2154                                 my $release_name = $entry;
2155                                 $release_name =~ s/ou=//g;
2156                                 if(length($parent)>0) {
2157                                         $release_name = $parent."/".$release_name;
2158                                 }
2159                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2160                                 foreach my $bufentry(@bufentries) {
2161                                         push @result, $bufentry;
2162                                 }
2163                         } else {
2164                                 my @types = get_fai_types($tmp_classes->{$entry});
2165                                 foreach my $type (@types) {
2166                                         push @result, 
2167                                         {
2168                                                 'class' => $entry,
2169                                                 'type' => $type->{'type'},
2170                                                 'release' => $parent,
2171                                                 'state' => $type->{'state'},
2172                                         };
2173                                 }
2174                         }
2175                 }
2176         }
2178         return @result;
2181 sub deep_copy {
2182         my $this = shift;
2183         if (not ref $this) {
2184                 $this;
2185         } elsif (ref $this eq "ARRAY") {
2186                 [map deep_copy($_), @$this];
2187         } elsif (ref $this eq "HASH") {
2188                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2189         } else { die "what type is $_?" }
2193 sub session_run_result {
2194     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2195     $kernel->sig(CHLD => "child_reap");
2198 sub session_run_debug {
2199     my $result = $_[ARG0];
2200     print STDERR "$result\n";
2203 sub session_run_done {
2204     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2205     delete $heap->{task}->{$task_id};
2209 sub create_sources_list {
2210         my $session_id = shift;
2211         my $ldap_handle = &main::get_ldap_handle;
2212         my $result="/tmp/gosa_si_tmp_sources_list";
2214         # Remove old file
2215         if(stat($result)) {
2216                 unlink($result);
2217                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2218         }
2220         my $fh;
2221         open($fh, ">$result");
2222         if (not defined $fh) {
2223                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2224                 return undef;
2225         }
2226         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2227                 my $mesg=$ldap_handle->search(
2228                         base    => $main::ldap_server_dn,
2229                         scope   => 'base',
2230                         attrs   => 'FAIrepository',
2231                         filter  => 'objectClass=FAIrepositoryServer'
2232                 );
2233                 if($mesg->count) {
2234                         foreach my $entry(@{$mesg->{'entries'}}) {
2235                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2236                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2237                                         my $line = "deb $server $release";
2238                                         $sections =~ s/,/ /g;
2239                                         $line.= " $sections";
2240                                         print $fh $line."\n";
2241                                 }
2242                         }
2243                 }
2244         } else {
2245                 if (defined $main::ldap_server_dn){
2246                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2247                 } else {
2248                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2249                 }
2250         }
2251         close($fh);
2253         return $result;
2257 sub run_create_packages_list_db {
2258     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2259         my $session_id = $session->ID;
2261         my $task = POE::Wheel::Run->new(
2262                                         Priority => +20,
2263                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2264                                         StdoutEvent  => "session_run_result",
2265                                         StderrEvent  => "session_run_debug",
2266                                         CloseEvent   => "session_run_done",
2267                                         );
2268         $heap->{task}->{ $task->ID } = $task;
2272 sub create_packages_list_db {
2273         my ($ldap_handle, $sources_file, $session_id) = @_;
2274         
2275         # it should not be possible to trigger a recreation of packages_list_db
2276         # while packages_list_db is under construction, so set flag packages_list_under_construction
2277         # which is tested befor recreation can be started
2278         if (-r $packages_list_under_construction) {
2279                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2280                 return;
2281         } else {
2282                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2283                 # set packages_list_under_construction to true
2284                 system("touch $packages_list_under_construction");
2285                 @packages_list_statements=();
2286         }
2288         if (not defined $session_id) { $session_id = 0; }
2289         if (not defined $ldap_handle) { 
2290                 $ldap_handle= &get_ldap_handle();
2292                 if (not defined $ldap_handle) {
2293                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2294                         unlink($packages_list_under_construction);
2295                         return;
2296                 }
2297         }
2298         if (not defined $sources_file) { 
2299                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2300                 $sources_file = &create_sources_list($session_id);
2301         }
2303         if (not defined $sources_file) {
2304                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2305                 unlink($packages_list_under_construction);
2306                 return;
2307         }
2309         my $line;
2311         open(CONFIG, "<$sources_file") or do {
2312                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2313                 unlink($packages_list_under_construction);
2314                 return;
2315         };
2317         # Read lines
2318         while ($line = <CONFIG>){
2319                 # Unify
2320                 chop($line);
2321                 $line =~ s/^\s+//;
2322                 $line =~ s/^\s+/ /;
2324                 # Strip comments
2325                 $line =~ s/#.*$//g;
2327                 # Skip empty lines
2328                 if ($line =~ /^\s*$/){
2329                         next;
2330                 }
2332                 # Interpret deb line
2333                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2334                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2335                         my $section;
2336                         foreach $section (split(' ', $sections)){
2337                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2338                         }
2339                 }
2340         }
2342         close (CONFIG);
2344         find(\&cleanup_and_extract, keys( %repo_dirs ));
2345         &main::strip_packages_list_statements();
2346         unshift @packages_list_statements, "VACUUM";
2347         $packages_list_db->exec_statementlist(\@packages_list_statements);
2348         unlink($packages_list_under_construction);
2349         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2350         return;
2353 # This function should do some intensive task to minimize the db-traffic
2354 sub strip_packages_list_statements {
2355     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2356         my @new_statement_list=();
2357         my $hash;
2358         my $insert_hash;
2359         my $update_hash;
2360         my $delete_hash;
2361         my $local_timestamp=get_time();
2363         foreach my $existing_entry (@existing_entries) {
2364                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2365         }
2367         foreach my $statement (@packages_list_statements) {
2368                 if($statement =~ /^INSERT/i) {
2369                         # Assign the values from the insert statement
2370                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2371                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2372                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2373                                 # If section or description has changed, update the DB
2374                                 if( 
2375                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2376                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2377                                 ) {
2378                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2379                                 }
2380                         } else {
2381                                 # Insert a non-existing entry to db
2382                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2383                         }
2384                 } elsif ($statement =~ /^UPDATE/i) {
2385                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2386                         /^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;
2387                         foreach my $distribution (keys %{$hash}) {
2388                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2389                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2390                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2391                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2392                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2393                                                 my $section;
2394                                                 my $description;
2395                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2396                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2397                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2398                                                 }
2399                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2400                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2401                                                 }
2402                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2403                                         }
2404                                 }
2405                         }
2406                 }
2407         }
2409         # TODO: Check for orphaned entries
2411         # unroll the insert_hash
2412         foreach my $distribution (keys %{$insert_hash}) {
2413                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2414                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2415                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2416                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2417                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2418                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2419                                 ."'$local_timestamp')";
2420                         }
2421                 }
2422         }
2424         # unroll the update hash
2425         foreach my $distribution (keys %{$update_hash}) {
2426                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2427                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2428                                 my $set = "";
2429                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2430                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2431                                 }
2432                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2433                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2434                                 }
2435                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2436                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2437                                 }
2438                                 if(defined($set) and length($set) > 0) {
2439                                         $set .= "timestamp = '$local_timestamp'";
2440                                 } else {
2441                                         next;
2442                                 }
2443                                 push @new_statement_list, 
2444                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2445                                         ." distribution = '$distribution'"
2446                                         ." AND package = '$package'"
2447                                         ." AND version = '$version'";
2448                         }
2449                 }
2450         }
2452         @packages_list_statements = @new_statement_list;
2456 sub parse_package_info {
2457     my ($baseurl, $dist, $section, $session_id)= @_;
2458     my ($package);
2459     if (not defined $session_id) { $session_id = 0; }
2460     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2461     $repo_dirs{ "${repo_path}/pool" } = 1;
2463     foreach $package ("Packages.gz"){
2464         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2465         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2466         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2467     }
2468     
2472 sub get_package {
2473     my ($url, $dest, $session_id)= @_;
2474     if (not defined $session_id) { $session_id = 0; }
2476     my $tpath = dirname($dest);
2477     -d "$tpath" || mkpath "$tpath";
2479     # This is ugly, but I've no time to take a look at "how it works in perl"
2480     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2481         system("gunzip -cd '$dest' > '$dest.in'");
2482         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2483         unlink($dest);
2484         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2485     } else {
2486         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2487     }
2488     return 0;
2492 sub parse_package {
2493     my ($path, $dist, $srv_path, $session_id)= @_;
2494     if (not defined $session_id) { $session_id = 0;}
2495     my ($package, $version, $section, $description);
2496     my $PACKAGES;
2497     my $timestamp = &get_time();
2499     if(not stat("$path.in")) {
2500         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2501         return;
2502     }
2504     open($PACKAGES, "<$path.in");
2505     if(not defined($PACKAGES)) {
2506         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2507         return;
2508     }
2510     # Read lines
2511     while (<$PACKAGES>){
2512         my $line = $_;
2513         # Unify
2514         chop($line);
2516         # Use empty lines as a trigger
2517         if ($line =~ /^\s*$/){
2518             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2519             push(@packages_list_statements, $sql);
2520             $package = "none";
2521             $version = "none";
2522             $section = "none";
2523             $description = "none"; 
2524             next;
2525         }
2527         # Trigger for package name
2528         if ($line =~ /^Package:\s/){
2529             ($package)= ($line =~ /^Package: (.*)$/);
2530             next;
2531         }
2533         # Trigger for version
2534         if ($line =~ /^Version:\s/){
2535             ($version)= ($line =~ /^Version: (.*)$/);
2536             next;
2537         }
2539         # Trigger for description
2540         if ($line =~ /^Description:\s/){
2541             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2542             next;
2543         }
2545         # Trigger for section
2546         if ($line =~ /^Section:\s/){
2547             ($section)= ($line =~ /^Section: (.*)$/);
2548             next;
2549         }
2551         # Trigger for filename
2552         if ($line =~ /^Filename:\s/){
2553             my ($filename) = ($line =~ /^Filename: (.*)$/);
2554             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2555             next;
2556         }
2557     }
2559     close( $PACKAGES );
2560     unlink( "$path.in" );
2561     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2565 sub store_fileinfo {
2566     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2568     my %fileinfo = (
2569         'package' => $package,
2570         'dist' => $dist,
2571         'version' => $vers,
2572     );
2574     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2578 sub cleanup_and_extract {
2579     my $fileinfo = $repo_files{ $File::Find::name };
2581     if( defined $fileinfo ) {
2583         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2584         my $sql;
2585         my $package = $fileinfo->{ 'package' };
2586         my $newver = $fileinfo->{ 'version' };
2588         mkpath($dir);
2589         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2591                 if( -f "$dir/DEBIAN/templates" ) {
2593                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2595                         my $tmpl= "";
2596                         {
2597                                 local $/=undef;
2598                                 open FILE, "$dir/DEBIAN/templates";
2599                                 $tmpl = &encode_base64(<FILE>);
2600                                 close FILE;
2601                         }
2602                         rmtree("$dir/DEBIAN/templates");
2604                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2605                 push @packages_list_statements, $sql;
2606                 }
2607     }
2609     return;
2613 sub register_at_foreign_servers {   
2614     my ($kernel) = $_[KERNEL];
2616     # hole alle bekannten server aus known_server_db
2617     my $server_sql = "SELECT * FROM $known_server_tn";
2618     my $server_res = $known_server_db->exec_statement($server_sql);
2620     # no entries in known_server_db
2621     if (not ref(@$server_res[0]) eq "ARRAY") { 
2622         # TODO
2623     }
2625     # detect already connected clients
2626     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2627     my $client_res = $known_clients_db->exec_statement($client_sql);
2629     # send my server details to all other gosa-si-server within the network
2630     foreach my $hit (@$server_res) {
2631         my $hostname = @$hit[0];
2632         my $hostkey = &create_passwd;
2634         # add already connected clients to registration message 
2635         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2636         &add_content2xml_hash($myhash, 'key', $hostkey);
2637         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2638         
2639         # build registration message and send it
2640         my $foreign_server_msg = &create_xml_string($myhash);
2641         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2642     }
2643     
2644     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2645     return;
2649 #==== MAIN = main ==============================================================
2650 #  parse commandline options
2651 Getopt::Long::Configure( "bundling" );
2652 GetOptions("h|help" => \&usage,
2653         "c|config=s" => \$cfg_file,
2654         "f|foreground" => \$foreground,
2655         "v|verbose+" => \$verbose,
2656         "no-bus+" => \$no_bus,
2657         "no-arp+" => \$no_arp,
2658            );
2660 #  read and set config parameters
2661 &check_cmdline_param ;
2662 &read_configfile;
2663 &check_pid;
2665 $SIG{CHLD} = 'IGNORE';
2667 # forward error messages to logfile
2668 if( ! $foreground ) {
2669   open( STDIN,  '+>/dev/null' );
2670   open( STDOUT, '+>&STDIN'    );
2671   open( STDERR, '+>&STDIN'    );
2674 # Just fork, if we are not in foreground mode
2675 if( ! $foreground ) { 
2676     chdir '/'                 or die "Can't chdir to /: $!";
2677     $pid = fork;
2678     setsid                    or die "Can't start a new session: $!";
2679     umask 0;
2680 } else { 
2681     $pid = $$; 
2684 # Do something useful - put our PID into the pid_file
2685 if( 0 != $pid ) {
2686     open( LOCK_FILE, ">$pid_file" );
2687     print LOCK_FILE "$pid\n";
2688     close( LOCK_FILE );
2689     if( !$foreground ) { 
2690         exit( 0 ) 
2691     };
2694 # parse head url and revision from svn
2695 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2696 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2697 $server_headURL = defined $1 ? $1 : 'unknown' ;
2698 $server_revision = defined $2 ? $2 : 'unknown' ;
2699 if ($server_headURL =~ /\/tag\// || 
2700         $server_headURL =~ /\/branches\// ) {
2701     $server_status = "stable"; 
2702 } else {
2703     $server_status = "developmental" ;
2707 daemon_log(" ", 1);
2708 daemon_log("$0 started!", 1);
2709 daemon_log("status: $server_status", 1);
2710 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2712 if ($no_bus > 0) {
2713     $bus_activ = "false"
2716 # connect to incoming_db
2717 unlink($incoming_file_name);
2718 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2719 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2721 # connect to gosa-si job queue
2722 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2723 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2725 # connect to known_clients_db
2726 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2727 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2729 # connect to foreign_clients_db
2730 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2731 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2733 # connect to known_server_db
2734 unlink($known_server_file_name);
2735 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2736 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2738 # connect to login_usr_db
2739 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2740 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2742 # connect to fai_server_db and fai_release_db
2743 unlink($fai_server_file_name);
2744 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2745 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2747 unlink($fai_release_file_name);
2748 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2749 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2751 # connect to packages_list_db
2752 #unlink($packages_list_file_name);
2753 unlink($packages_list_under_construction);
2754 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2755 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2757 # connect to messaging_db
2758 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2759 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2762 # create xml object used for en/decrypting
2763 $xml = new XML::Simple();
2766 # foreign servers 
2767 my @foreign_server_list;
2769 # add foreign server from cfg file
2770 if ($foreign_server_string ne "") {
2771     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2772     foreach my $foreign_server (@cfg_foreign_server_list) {
2773         push(@foreign_server_list, $foreign_server);
2774     }
2777 # add foreign server from dns
2778 my @tmp_servers;
2779 if ( !$server_domain) {
2780     # Try our DNS Searchlist
2781     for my $domain(get_dns_domains()) {
2782         chomp($domain);
2783         my @tmp_domains= &get_server_addresses($domain);
2784         if(@tmp_domains) {
2785             for my $tmp_server(@tmp_domains) {
2786                 push @tmp_servers, $tmp_server;
2787             }
2788         }
2789     }
2790     if(@tmp_servers && length(@tmp_servers)==0) {
2791         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2792     }
2793 } else {
2794     @tmp_servers = &get_server_addresses($server_domain);
2795     if( 0 == @tmp_servers ) {
2796         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2797     }
2799 foreach my $server (@tmp_servers) { 
2800     unshift(@foreign_server_list, $server); 
2802 # eliminate duplicate entries
2803 @foreign_server_list = &del_doubles(@foreign_server_list);
2804 my $all_foreign_server = join(", ", @foreign_server_list);
2805 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2807 # add all found foreign servers to known_server
2808 my $act_timestamp = &get_time();
2809 foreach my $foreign_server (@foreign_server_list) {
2810     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2811             primkey=>['hostname'],
2812             hostname=>$foreign_server,
2813             status=>'not_jet_registered',
2814             hostkey=>"none",
2815             timestamp=>$act_timestamp,
2816             } );
2820 POE::Component::Server::TCP->new(
2821         Port => $server_port,
2822         ClientInput => sub {
2823         my ($kernel, $input) = @_[KERNEL, ARG0];
2824         push(@tasks, $input);
2825         push(@msgs_to_decrypt, $input);
2826         $kernel->yield("msg_to_decrypt");
2827         $kernel->yield("next_task");
2828         },
2829     InlineStates => {
2830         next_task => \&next_task,
2831         msg_to_decrypt => \&msg_to_decrypt,
2832         task_result => \&handle_task_result,
2833         task_done   => \&handle_task_done,
2834         task_debug  => \&handle_task_debug,
2835         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2836     }
2837 );
2839 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2841 # create session for repeatedly checking the job queue for jobs
2842 POE::Session->create(
2843         inline_states => {
2844                 _start => \&_start,
2845         register_at_foreign_servers => \&register_at_foreign_servers,
2846                 sig_handler => \&sig_handler,
2847         watch_for_new_messages => \&watch_for_new_messages,
2848         watch_for_delivery_messages => \&watch_for_delivery_messages,
2849         watch_for_done_messages => \&watch_for_done_messages,
2850                 watch_for_new_jobs => \&watch_for_new_jobs,
2851         watch_for_done_jobs => \&watch_for_done_jobs,
2852         watch_for_old_known_clients => \&watch_for_old_known_clients,
2853         create_packages_list_db => \&run_create_packages_list_db,
2854         create_fai_server_db => \&run_create_fai_server_db,
2855         create_fai_release_db => \&run_create_fai_release_db,
2856         session_run_result => \&session_run_result,
2857         session_run_debug => \&session_run_debug,
2858         session_run_done => \&session_run_done,
2859         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2860         }
2861 );
2864 # import all modules
2865 &import_modules;
2867 # TODO
2868 # check wether all modules are gosa-si valid passwd check
2872 POE::Kernel->run();
2873 exit;