Code

bugfix: log file has permission 0600
[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");
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, 600],
252 );
255 #===  FUNCTION  ================================================================
256 #         NAME:  usage
257 #   PARAMETERS:  nothing
258 #      RETURNS:  nothing
259 #  DESCRIPTION:  print out usage text to STDERR
260 #===============================================================================
261 sub usage {
262     print STDERR << "EOF" ;
263 usage: $prg [-hvf] [-c config]
265            -h        : this (help) message
266            -c <file> : config file
267            -f        : foreground, process will not be forked to background
268            -v        : be verbose (multiple to increase verbosity)
269            -no-bus   : starts $prg without connection to bus
270            -no-arp   : starts $prg without connection to arp module
271  
272 EOF
273     print "\n" ;
277 #===  FUNCTION  ================================================================
278 #         NAME:  read_configfile
279 #   PARAMETERS:  cfg_file - string -
280 #      RETURNS:  nothing
281 #  DESCRIPTION:  read cfg_file and set variables
282 #===============================================================================
283 sub read_configfile {
284     my $cfg;
285     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
286         if( -r $cfg_file ) {
287             $cfg = Config::IniFiles->new( -file => $cfg_file );
288         } else {
289             print STDERR "Couldn't read config file!\n";
290         }
291     } else {
292         $cfg = Config::IniFiles->new() ;
293     }
294     foreach my $section (keys %cfg_defaults) {
295         foreach my $param (keys %{$cfg_defaults{ $section }}) {
296             my $pinfo = $cfg_defaults{ $section }{ $param };
297             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
298         }
299     }
303 #===  FUNCTION  ================================================================
304 #         NAME:  logging
305 #   PARAMETERS:  level - string - default 'info'
306 #                msg - string -
307 #                facility - string - default 'LOG_DAEMON'
308 #      RETURNS:  nothing
309 #  DESCRIPTION:  function for logging
310 #===============================================================================
311 sub daemon_log {
312     # log into log_file
313     my( $msg, $level ) = @_;
314     if(not defined $msg) { return }
315     if(not defined $level) { $level = 1 }
316     if(defined $log_file){
317         open(LOG_HANDLE, ">>$log_file");
318         chmod 0600, $log_file;
319         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
320             print STDERR "cannot open $log_file: $!";
321             return }
322             chomp($msg);
323                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
324             if($level <= $verbose){
325                 my ($seconds, $minutes, $hours, $monthday, $month,
326                         $year, $weekday, $yearday, $sommertime) = localtime(time);
327                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
328                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
329                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
330                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
331                 $month = $monthnames[$month];
332                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
333                 $year+=1900;
334                 my $name = $prg;
336                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
337                 print LOG_HANDLE $log_msg;
338                 if( $foreground ) { 
339                     print STDERR $log_msg;
340                 }
341             }
342         close( LOG_HANDLE );
343     }
347 #===  FUNCTION  ================================================================
348 #         NAME:  check_cmdline_param
349 #   PARAMETERS:  nothing
350 #      RETURNS:  nothing
351 #  DESCRIPTION:  validates commandline parameter
352 #===============================================================================
353 sub check_cmdline_param () {
354     my $err_config;
355     my $err_counter = 0;
356         if(not defined($cfg_file)) {
357                 $cfg_file = "/etc/gosa-si/server.conf";
358                 if(! -r $cfg_file) {
359                         $err_config = "please specify a config file";
360                         $err_counter += 1;
361                 }
362     }
363     if( $err_counter > 0 ) {
364         &usage( "", 1 );
365         if( defined( $err_config)) { print STDERR "$err_config\n"}
366         print STDERR "\n";
367         exit( -1 );
368     }
372 #===  FUNCTION  ================================================================
373 #         NAME:  check_pid
374 #   PARAMETERS:  nothing
375 #      RETURNS:  nothing
376 #  DESCRIPTION:  handels pid processing
377 #===============================================================================
378 sub check_pid {
379     $pid = -1;
380     # Check, if we are already running
381     if( open(LOCK_FILE, "<$pid_file") ) {
382         $pid = <LOCK_FILE>;
383         if( defined $pid ) {
384             chomp( $pid );
385             if( -f "/proc/$pid/stat" ) {
386                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
387                 if( $stat ) {
388                                         daemon_log("ERROR: Already running",1);
389                     close( LOCK_FILE );
390                     exit -1;
391                 }
392             }
393         }
394         close( LOCK_FILE );
395         unlink( $pid_file );
396     }
398     # create a syslog msg if it is not to possible to open PID file
399     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
400         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
401         if (open(LOCK_FILE, '<', $pid_file)
402                 && ($pid = <LOCK_FILE>))
403         {
404             chomp($pid);
405             $msg .= "(PID $pid)\n";
406         } else {
407             $msg .= "(unable to read PID)\n";
408         }
409         if( ! ($foreground) ) {
410             openlog( $0, "cons,pid", "daemon" );
411             syslog( "warning", $msg );
412             closelog();
413         }
414         else {
415             print( STDERR " $msg " );
416         }
417         exit( -1 );
418     }
421 #===  FUNCTION  ================================================================
422 #         NAME:  import_modules
423 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
424 #                are stored
425 #      RETURNS:  nothing
426 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
427 #                state is on is imported by "require 'file';"
428 #===============================================================================
429 sub import_modules {
430     daemon_log(" ", 1);
432     if (not -e $modules_path) {
433         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
434     }
436     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
437     while (defined (my $file = readdir (DIR))) {
438         if (not $file =~ /(\S*?).pm$/) {
439             next;
440         }
441                 my $mod_name = $1;
443         if( $file =~ /ArpHandler.pm/ ) {
444             if( $no_arp > 0 ) {
445                 next;
446             }
447         }
448         
449         eval { require $file; };
450         if ($@) {
451             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
452             daemon_log("$@", 5);
453                 } else {
454                         my $info = eval($mod_name.'::get_module_info()');
455                         # Only load module if get_module_info() returns a non-null object
456                         if( $info ) {
457                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
458                                 $known_modules->{$mod_name} = $info;
459                                 daemon_log("0 INFO: module $mod_name loaded", 5);
460                         }
461                 }
462     }   
463     close (DIR);
467 #===  FUNCTION  ================================================================
468 #         NAME:  sig_int_handler
469 #   PARAMETERS:  signal - string - signal arose from system
470 #      RETURNS:  noting
471 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
472 #===============================================================================
473 sub sig_int_handler {
474     my ($signal) = @_;
476 #       if (defined($ldap_handle)) {
477 #               $ldap_handle->disconnect;
478 #       }
479     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
480     
482     daemon_log("shutting down gosa-si-server", 1);
483     system("kill `ps -C gosa-si-server-nobus -o pid=`");
485 $SIG{INT} = \&sig_int_handler;
488 sub check_key_and_xml_validity {
489     my ($crypted_msg, $module_key, $session_id) = @_;
490     my $msg;
491     my $msg_hash;
492     my $error_string;
493     eval{
494         $msg = &decrypt_msg($crypted_msg, $module_key);
496         if ($msg =~ /<xml>/i){
497             $msg =~ s/\s+/ /g;  # just for better daemon_log
498             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
499             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
501             ##############
502             # check header
503             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
504             my $header_l = $msg_hash->{'header'};
505             if( 1 > @{$header_l} ) { die 'empty header tag'; }
506             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
507             my $header = @{$header_l}[0];
508             if( 0 == length $header) { die 'empty string in header tag'; }
510             ##############
511             # check source
512             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
513             my $source_l = $msg_hash->{'source'};
514             if( 1 > @{$source_l} ) { die 'empty source tag'; }
515             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
516             my $source = @{$source_l}[0];
517             if( 0 == length $source) { die 'source error'; }
519             ##############
520             # check target
521             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
522             my $target_l = $msg_hash->{'target'};
523             if( 1 > @{$target_l} ) { die 'empty target tag'; }
524         }
525     };
526     if($@) {
527         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
528         $msg = undef;
529         $msg_hash = undef;
530     }
532     return ($msg, $msg_hash);
536 sub check_outgoing_xml_validity {
537     my ($msg) = @_;
539     my $msg_hash;
540     eval{
541         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
543         ##############
544         # check header
545         my $header_l = $msg_hash->{'header'};
546         if( 1 != @{$header_l} ) {
547             die 'no or more than one headers specified';
548         }
549         my $header = @{$header_l}[0];
550         if( 0 == length $header) {
551             die 'header has length 0';
552         }
554         ##############
555         # check source
556         my $source_l = $msg_hash->{'source'};
557         if( 1 != @{$source_l} ) {
558             die 'no or more than 1 sources specified';
559         }
560         my $source = @{$source_l}[0];
561         if( 0 == length $source) {
562             die 'source has length 0';
563         }
564         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
565                 $source =~ /^GOSA$/i ) {
566             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
567         }
568         
569         ##############
570         # check target  
571         my $target_l = $msg_hash->{'target'};
572         if( 0 == @{$target_l} ) {
573             die "no targets specified";
574         }
575         foreach my $target (@$target_l) {
576             if( 0 == length $target) {
577                 die "target has length 0";
578             }
579             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
580                     $target =~ /^GOSA$/i ||
581                     $target =~ /^\*$/ ||
582                     $target =~ /KNOWN_SERVER/i ||
583                     $target =~ /JOBDB/i ||
584                     $target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ){
585                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
586             }
587         }
588     };
589     if($@) {
590         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
591         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
592         $msg_hash = undef;
593     }
595     return ($msg_hash);
599 sub input_from_known_server {
600     my ($input, $remote_ip, $session_id) = @_ ;  
601     my ($msg, $msg_hash, $module);
603     my $sql_statement= "SELECT * FROM known_server";
604     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
606     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
607         my $host_name = $hit->{hostname};
608         if( not $host_name =~ "^$remote_ip") {
609             next;
610         }
611         my $host_key = $hit->{hostkey};
612         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
613         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
615         # check if module can open msg envelope with module key
616         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
617         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
618             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
619             daemon_log("$@", 8);
620             next;
621         }
622         else {
623             $msg = $tmp_msg;
624             $msg_hash = $tmp_msg_hash;
625             $module = "ServerPackages";
626             last;
627         }
628     }
630     if( (!$msg) || (!$msg_hash) || (!$module) ) {
631         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
632     }
633   
634     return ($msg, $msg_hash, $module);
638 sub input_from_known_client {
639     my ($input, $remote_ip, $session_id) = @_ ;  
640     my ($msg, $msg_hash, $module);
642     my $sql_statement= "SELECT * FROM known_clients";
643     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
644     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
645         my $host_name = $hit->{hostname};
646         if( not $host_name =~ /^$remote_ip:\d*$/) {
647                 next;
648                 }
649         my $host_key = $hit->{hostkey};
650         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
651         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
653         # check if module can open msg envelope with module key
654         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
656         if( (!$msg) || (!$msg_hash) ) {
657             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
658             &daemon_log("$@", 8);
659             next;
660         }
661         else {
662             $module = "ClientPackages";
663             last;
664         }
665     }
667     if( (!$msg) || (!$msg_hash) || (!$module) ) {
668         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
669     }
671     return ($msg, $msg_hash, $module);
675 sub input_from_unknown_host {
676     no strict "refs";
677     my ($input, $session_id) = @_ ;
678     my ($msg, $msg_hash, $module);
679     my $error_string;
680     
681         my %act_modules = %$known_modules;
682         
683     while( my ($mod, $info) = each(%act_modules)) {
685         # check a key exists for this module
686         my $module_key = ${$mod."_key"};
687         if( not defined $module_key ) {
688             if( $mod eq 'ArpHandler' ) {
689                 next;
690             }
691             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
692             next;
693         }
694         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
696         # check if module can open msg envelope with module key
697         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
698         if( (not defined $msg) || (not defined $msg_hash) ) {
699             next;
700         }
701         else {
702             $module = $mod;
703             last;
704         }
705     }
707     if( (!$msg) || (!$msg_hash) || (!$module)) {
708         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
709     }
711     return ($msg, $msg_hash, $module);
715 sub create_ciphering {
716     my ($passwd) = @_;
717         if((!defined($passwd)) || length($passwd)==0) {
718                 $passwd = "";
719         }
720     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
721     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
722     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
723     $my_cipher->set_iv($iv);
724     return $my_cipher;
728 sub encrypt_msg {
729     my ($msg, $key) = @_;
730     my $my_cipher = &create_ciphering($key);
731     my $len;
732     {
733             use bytes;
734             $len= 16-length($msg)%16;
735     }
736     $msg = "\0"x($len).$msg;
737     $msg = $my_cipher->encrypt($msg);
738     chomp($msg = &encode_base64($msg));
739     # there are no newlines allowed inside msg
740     $msg=~ s/\n//g;
741     return $msg;
745 sub decrypt_msg {
747     my ($msg, $key) = @_ ;
748     $msg = &decode_base64($msg);
749     my $my_cipher = &create_ciphering($key);
750     $msg = $my_cipher->decrypt($msg); 
751     $msg =~ s/\0*//g;
752     return $msg;
756 sub get_encrypt_key {
757     my ($target) = @_ ;
758     my $encrypt_key;
759     my $error = 0;
761     # target can be in known_server
762     if( not defined $encrypt_key ) {
763         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
764         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
765         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
766             my $host_name = $hit->{hostname};
767             if( $host_name ne $target ) {
768                 next;
769             }
770             $encrypt_key = $hit->{hostkey};
771             last;
772         }
773     }
775     # target can be in known_client
776     if( not defined $encrypt_key ) {
777         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
778         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
779         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
780             my $host_name = $hit->{hostname};
781             if( $host_name ne $target ) {
782                 next;
783             }
784             $encrypt_key = $hit->{hostkey};
785             last;
786         }
787     }
789     return $encrypt_key;
793 #===  FUNCTION  ================================================================
794 #         NAME:  open_socket
795 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
796 #                [PeerPort] string necessary if port not appended by PeerAddr
797 #      RETURNS:  socket IO::Socket::INET
798 #  DESCRIPTION:  open a socket to PeerAddr
799 #===============================================================================
800 sub open_socket {
801     my ($PeerAddr, $PeerPort) = @_ ;
802     if(defined($PeerPort)){
803         $PeerAddr = $PeerAddr.":".$PeerPort;
804     }
805     my $socket;
806     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
807             Porto => "tcp",
808             Type => SOCK_STREAM,
809             Timeout => 5,
810             );
811     if(not defined $socket) {
812         return;
813     }
814 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
815     return $socket;
819 #===  FUNCTION  ================================================================
820 #         NAME:  get_ip 
821 #   PARAMETERS:  interface name (i.e. eth0)
822 #      RETURNS:  (ip address) 
823 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
824 #===============================================================================
825 sub get_ip {
826         my $ifreq= shift;
827         my $result= "";
828         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
829         my $proto= getprotobyname('ip');
831         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
832                 or die "socket: $!";
834         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
835                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
836                 my ($port, $addr) = sockaddr_in $sin;
837                 my $ip            = inet_ntoa $addr;
839                 if ($ip && length($ip) > 0) {
840                         $result = $ip;
841                 }
842         }
844         return $result;
848 sub get_local_ip_for_remote_ip {
849         my $remote_ip= shift;
850         my $result="0.0.0.0";
852         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
853                 if($remote_ip eq "127.0.0.1") {
854                         $result = "127.0.0.1";
855                 } else {
856                         my $PROC_NET_ROUTE= ('/proc/net/route');
858                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
859                                 or die "Could not open $PROC_NET_ROUTE";
861                         my @ifs = <PROC_NET_ROUTE>;
863                         close(PROC_NET_ROUTE);
865                         # Eat header line
866                         shift @ifs;
867                         chomp @ifs;
868                         foreach my $line(@ifs) {
869                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
870                                 my $destination;
871                                 my $mask;
872                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
873                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
875                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
877                                         # destination matches route, save mac and exit
878                                         $result= &get_ip($Iface);
879                                         last;
880                                 }
881                         }
882                 }
883         } else {
884                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
885         }
886         return $result;
890 sub send_msg_to_target {
891     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
892     my $error = 0;
893     my $header;
894     my $new_status;
895     my $act_status;
896     my ($sql_statement, $res);
897   
898     if( $msg_header ) {
899         $header = "'$msg_header'-";
900     } else {
901         $header = "";
902     }
904         # Patch the source ip
905         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
906                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
907                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
908         }
910     # encrypt xml msg
911     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
913     # opensocket
914     my $socket = &open_socket($address);
915     if( !$socket ) {
916         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
917         $error++;
918     }
919     
920     if( $error == 0 ) {
921         # send xml msg
922         print $socket $crypted_msg."\n";
924         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
925         daemon_log("DEBUG: message:\n$msg", 9);
926         
927     }
929     # close socket in any case
930     if( $socket ) {
931         close $socket;
932     }
934     if( $error > 0 ) { $new_status = "down"; }
935     else { $new_status = $msg_header; }
938     # known_clients
939     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
940     $res = $known_clients_db->select_dbentry($sql_statement);
941     if( keys(%$res) > 0) {
942         $act_status = $res->{1}->{'status'};
943         if ($act_status eq "down" && $new_status eq "down") {
944             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
945             $res = $known_clients_db->del_dbentry($sql_statement);
946             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
947         } else { 
948             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
949             $res = $known_clients_db->update_dbentry($sql_statement);
950             if($new_status eq "down"){
951                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
952             } else {
953                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
954             }
955         }
956     }
958     # known_server
959     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
960     $res = $known_server_db->select_dbentry($sql_statement);
961     if( keys(%$res) > 0 ) {
962         $act_status = $res->{1}->{'status'};
963         if ($act_status eq "down" && $new_status eq "down") {
964             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
965             $res = $known_server_db->del_dbentry($sql_statement);
966             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
967         } 
968         else { 
969             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
970             $res = $known_server_db->update_dbentry($sql_statement);
971             if($new_status eq "down"){
972                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
973             }
974             else {
975                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
976             }
977         }
978     }
979     return $error; 
983 sub update_jobdb_status_for_send_msgs {
984     my ($answer, $error) = @_;
985     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
986         my $jobdb_id = $1;
987             
988         # sending msg faild
989         if( $error ) {
990             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
991                 my $sql_statement = "UPDATE $job_queue_tn ".
992                     "SET status='error', result='can not deliver msg, please consult log file' ".
993                     "WHERE id=$jobdb_id";
994                 my $res = $job_db->update_dbentry($sql_statement);
995             }
997         # sending msg was successful
998         } else {
999             my $sql_statement = "UPDATE $job_queue_tn ".
1000                 "SET status='done' ".
1001                 "WHERE id=$jobdb_id AND status='processed'";
1002             my $res = $job_db->update_dbentry($sql_statement);
1003         }
1004     }
1007 sub _start {
1008     my ($kernel) = $_[KERNEL];
1009     &trigger_db_loop($kernel);
1010     $global_kernel = $kernel;
1011     $kernel->yield('register_at_foreign_servers');
1012         $kernel->yield('create_fai_server_db', $fai_server_tn );
1013         $kernel->yield('create_fai_release_db', $fai_release_tn );
1014         $kernel->sig(USR1 => "sig_handler");
1015         $kernel->sig(USR2 => "create_packages_list_db");
1018 sub sig_handler {
1019         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1020         daemon_log("0 INFO got signal '$signal'", 1); 
1021         $kernel->sig_handled();
1022         return;
1026 sub msg_to_decrypt {
1027     my ($session, $heap) = @_[SESSION, HEAP];
1028     my $session_id = $session->ID;
1029     my ($msg, $msg_hash, $module);
1030     my $error = 0;
1032     # hole neue msg aus @msgs_to_decrypt
1033     my $next_msg = shift @msgs_to_decrypt;
1034     
1035     # entschlüssle sie
1037     # msg is from a new client or gosa
1038     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1039     # msg is from a gosa-si-server or gosa-si-bus
1040     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1042     }
1043     # msg is from a gosa-si-client
1044     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1045         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1046     }
1047     # an error occurred
1048     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1050         # could not understand a msg from its server the client cause a re-registering process
1051         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1052         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1053         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1054         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1055             my $host_name = $hit->{'hostname'};
1056             my $host_key = $hit->{'hostkey'};
1057             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1058             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1059             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1060         }
1061         $error++;
1062     }
1063     
1064     # add message to incoming_db
1065     if( $error == 0) {
1066         my $header = @{$msg_hash->{'header'}}[0];
1067         my $target = @{$msg_hash->{'target'}}[0];
1068         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1069                 primkey=>[],
1070                 headertag=>$header,
1071                                 targettag=>$target,
1072                 xmlmessage=>$msg,
1073                 timestamp=>&get_time,
1074                 module=>$module,
1075                 } );
1076         if ($res != 0) {
1077                         # TODO ist das mit $! so ok???
1078             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1079         }
1080     }
1085 sub next_task {
1086     my ($session, $heap) = @_[SESSION, HEAP];
1087     my $task = POE::Wheel::Run->new(
1088             Program => sub { process_task($session, $heap) },
1089             StdioFilter => POE::Filter::Reference->new(),
1090             StdoutEvent  => "task_result",
1091             StderrEvent  => "task_debug",
1092             CloseEvent   => "task_done",
1093             );
1095     $heap->{task}->{ $task->ID } = $task;
1098 sub handle_task_result {
1099     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1100     my $client_answer = $result->{'answer'};
1101     if( $client_answer =~ s/session_id=(\d+)$// ) {
1102         my $session_id = $1;
1103         if( defined $session_id ) {
1104             my $session_reference = $kernel->ID_id_to_session($session_id);
1105             if( defined $session_reference ) {
1106                 $heap = $session_reference->get_heap();
1107             }
1108         }
1110         if(exists $heap->{'client'}) {
1111             $heap->{'client'}->put($client_answer);
1112         }
1113     }
1114     $kernel->sig(CHLD => "child_reap");
1117 sub handle_task_debug {
1118     my $result = $_[ARG0];
1119     print STDERR "$result\n";
1122 sub handle_task_done {
1123     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1124     delete $heap->{task}->{$task_id};
1127 sub process_task {
1128     no strict "refs";
1129     my ($session, $heap, $input) = @_;
1130     my $session_id = $session->ID;
1131     my $error = 0;
1132     my $answer_l;
1133     my ($answer_header, @answer_target_l, $answer_source);
1134     my $client_answer = "";
1136         ##################################################
1137         # fetch first unprocessed message from incoming_db
1138     # sometimes the program is faster than sqlite, so wait until informations are present at db
1139     my $id_sql;
1140     my $id_res;
1141     my $message_id;
1142 # TODO : das hier ist sehr sehr unschön        
1143 # to be tested: speed enhancement with usleep 100000???
1144     while (1) {
1145         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1146         $id_res = $incoming_db->exec_statement($id_sql);
1147         $message_id = @{@$id_res[0]}[0];
1148         if (defined $message_id) { last }
1149         usleep(100000);
1150     }
1152     # fetch new message from incoming_db
1153     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1154     my $res = $incoming_db->exec_statement($sql);
1156     # prepare all variables needed to process message
1157     my $msg = @{@$res[0]}[4];
1158     my $incoming_id = @{@$res[0]}[0];
1159     my $module = @{@$res[0]}[5];
1160     my $header =  @{@$res[0]}[2];
1161     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1163     # messages which are an answer to a still running process should not be processed here
1164     if ($header =~ /^answer_(\d+)/) {
1165         return;
1166     }
1167    
1168     # delete message from db 
1169     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1170     my $delete_res = $incoming_db->exec_statement($delete_sql);
1172     ######################
1173     # process incoming msg
1174     if( $error == 0) {
1175         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1176                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1177         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1178         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1180         if ( 0 < @{$answer_l} ) {
1181             my $answer_str = join("\n", @{$answer_l});
1182             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1183                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1184             }
1185             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1186         } else {
1187             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1188         }
1190     }
1191     if( !$answer_l ) { $error++ };
1193     ########
1194     # answer
1195     if( $error == 0 ) {
1197         foreach my $answer ( @{$answer_l} ) {
1198             # check outgoing msg to xml validity
1199             my $answer_hash = &check_outgoing_xml_validity($answer);
1200             if( not defined $answer_hash ) { next; }
1201             
1202             $answer_header = @{$answer_hash->{'header'}}[0];
1203             @answer_target_l = @{$answer_hash->{'target'}};
1204             $answer_source = @{$answer_hash->{'source'}}[0];
1206             # deliver msg to all targets 
1207             foreach my $answer_target ( @answer_target_l ) {
1209                 # targets of msg are all gosa-si-clients in known_clients_db
1210                 if( $answer_target eq "*" ) {
1211                     # answer is for all clients
1212                     my $sql_statement= "SELECT * FROM known_clients";
1213                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1214                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1215                         my $host_name = $hit->{hostname};
1216                         my $host_key = $hit->{hostkey};
1217                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1218                         &update_jobdb_status_for_send_msgs($answer, $error);
1219                     }
1220                 }
1222                 # targets of msg are all gosa-si-server in known_server_db
1223                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1224                     # answer is for all server in known_server
1225                     my $sql_statement= "SELECT * FROM known_server";
1226                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1227                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1228                         my $host_name = $hit->{hostname};
1229                         my $host_key = $hit->{hostkey};
1230                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1231                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1232                         &update_jobdb_status_for_send_msgs($answer, $error);
1233                     }
1234                 }
1236                 # target of msg is GOsa
1237                                 elsif( $answer_target eq "GOSA" ) {
1238                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1239                                         my $add_on = "";
1240                     if( defined $session_id ) {
1241                         $add_on = ".session_id=$session_id";
1242                     }
1243                     # answer is for GOSA and has to returned to connected client
1244                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1245                     $client_answer = $gosa_answer.$add_on;
1246                 }
1248                 # target of msg is job queue at this host
1249                 elsif( $answer_target eq "JOBDB") {
1250                     $answer =~ /<header>(\S+)<\/header>/;   
1251                     my $header;
1252                     if( defined $1 ) { $header = $1; }
1253                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1254                     &update_jobdb_status_for_send_msgs($answer, $error);
1255                 }
1257                 # target of msg is a mac address
1258                 elsif( $answer_target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ) {
1259                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1260                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1261                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1262                     my $found_ip_flag = 0;
1263                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1264                         my $host_name = $hit->{hostname};
1265                         my $host_key = $hit->{hostkey};
1266                         $answer =~ s/$answer_target/$host_name/g;
1267                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1268                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1269                         &update_jobdb_status_for_send_msgs($answer, $error);
1270                         $found_ip_flag++ ;
1271                     }   
1272                     if( $found_ip_flag == 0) {
1273                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1274                         if( $bus_activ eq "true" ) { 
1275                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1276                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1277                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1278                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1279                                 my $bus_address = $hit->{hostname};
1280                                 my $bus_key = $hit->{hostkey};
1281                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1282                                 &update_jobdb_status_for_send_msgs($answer, $error);
1283                                 last;
1284                             }
1285                         }
1287                     }
1289                 #  answer is for one specific host   
1290                 } else {
1291                     # get encrypt_key
1292                     my $encrypt_key = &get_encrypt_key($answer_target);
1293                     if( not defined $encrypt_key ) {
1294                         # unknown target, forward msg to bus
1295                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1296                         if( $bus_activ eq "true" ) { 
1297                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1298                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1299                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1300                             my $res_length = keys( %{$query_res} );
1301                             if( $res_length == 0 ){
1302                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1303                                         "no bus found in known_server", 3);
1304                             }
1305                             else {
1306                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1307                                     my $bus_key = $hit->{hostkey};
1308                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1309                                     &update_jobdb_status_for_send_msgs($answer, $error);
1310                                 }
1311                             }
1312                         }
1313                         next;
1314                     }
1315                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1316                     &update_jobdb_status_for_send_msgs($answer, $error);
1317                 }
1318             }
1319         }
1320     }
1322     my $filter = POE::Filter::Reference->new();
1323     my %result = ( 
1324             status => "seems ok to me",
1325             answer => $client_answer,
1326             );
1328     my $output = $filter->put( [ \%result ] );
1329     print @$output;
1335 sub trigger_db_loop {
1336         my ($kernel) = @_ ;
1337         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1338         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1339         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1340     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1341         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1345 sub watch_for_done_jobs {
1346     my ($kernel,$heap) = @_[KERNEL, HEAP];
1348     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1349         " WHERE status='done'";
1350         my $res = $job_db->select_dbentry( $sql_statement );
1352     while( my ($id, $hit) = each %{$res} ) {
1353         my $jobdb_id = $hit->{id};
1354         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1355         my $res = $job_db->del_dbentry($sql_statement); 
1356     }
1358     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1362 sub watch_for_new_jobs {
1363         if($watch_for_new_jobs_in_progress == 0) {
1364                 $watch_for_new_jobs_in_progress = 1;
1365                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1367                 # check gosa job queue for jobs with executable timestamp
1368                 my $timestamp = &get_time();
1369                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1370                 my $res = $job_db->exec_statement( $sql_statement );
1372                 # Merge all new jobs that would do the same actions
1373                 my @drops;
1374                 my $hits;
1375                 foreach my $hit (reverse @{$res} ) {
1376                         my $macaddress= lc @{$hit}[8];
1377                         my $headertag= @{$hit}[5];
1378                         if(
1379                                 defined($hits->{$macaddress}) &&
1380                                 defined($hits->{$macaddress}->{$headertag}) &&
1381                                 defined($hits->{$macaddress}->{$headertag}[0])
1382                         ) {
1383                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1384                         }
1385                         $hits->{$macaddress}->{$headertag}= $hit;
1386                 }
1388                 # Delete new jobs with a matching job in state 'processing'
1389                 foreach my $macaddress (keys %{$hits}) {
1390                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1391                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1392                                 if(defined($jobdb_id)) {
1393                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1394                                         my $res = $job_db->exec_statement( $sql_statement );
1395                                         foreach my $hit (@{$res}) {
1396                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1397                                         }
1398                                 } else {
1399                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1400                                 }
1401                         }
1402                 }
1404                 # Commit deletion
1405                 $job_db->exec_statementlist(\@drops);
1407                 # Look for new jobs that could be executed
1408                 foreach my $macaddress (keys %{$hits}) {
1410                         # Look if there is an executing job
1411                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1412                         my $res = $job_db->exec_statement( $sql_statement );
1414                         # Skip new jobs for host if there is a processing job
1415                         if(defined($res) and defined @{$res}[0]) {
1416                                 next;
1417                         }
1419                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1420                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1421                                 if(defined($jobdb_id)) {
1422                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1424                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1425                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1426                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1428                                         # expect macaddress is unique!!!!!!
1429                                         my $target = $res_hash->{1}->{hostname};
1431                                         # change header
1432                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1434                                         # add sqlite_id
1435                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1437                                         $job_msg =~ /<header>(\S+)<\/header>/;
1438                                         my $header = $1 ;
1439                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1441                                         # update status in job queue to 'processing'
1442                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1443                                         my $res = $job_db->update_dbentry($sql_statement);
1444 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1446                                         # We don't want parallel processing
1447                                         last;
1448                                 }
1449                         }
1450                 }
1452                 $watch_for_new_jobs_in_progress = 0;
1453                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1454         }
1458 sub watch_for_new_messages {
1459     my ($kernel,$heap) = @_[KERNEL, HEAP];
1460     my @coll_user_msg;   # collection list of outgoing messages
1461     
1462     # check messaging_db for new incoming messages with executable timestamp
1463     my $timestamp = &get_time();
1464     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1465     my $res = $messaging_db->exec_statement( $sql_statement );
1466         foreach my $hit (@{$res}) {
1468         # create outgoing messages
1469         my $message_to = @{$hit}[3];
1470         # translate message_to to plain login name
1471         my @message_to_l = split(/,/, $message_to);  
1472                 my %receiver_h; 
1473                 foreach my $receiver (@message_to_l) {
1474                         if ($receiver =~ /^u_([\s\S]*)$/) {
1475                                 $receiver_h{$1} = 0;
1476                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1477                                 my $group_name = $1;
1478                                 # fetch all group members from ldap and add them to receiver hash
1479                                 my $ldap_handle = &get_ldap_handle();
1480                                 if (defined $ldap_handle) {
1481                                                 my $mesg = $ldap_handle->search(
1482                                                                                 base => $ldap_base,
1483                                                                                 scope => 'sub',
1484                                                                                 attrs => ['memberUid'],
1485                                                                                 filter => "cn=$group_name",
1486                                                                                 );
1487                                                 if ($mesg->count) {
1488                                                                 my @entries = $mesg->entries;
1489                                                                 foreach my $entry (@entries) {
1490                                                                                 my @receivers= $entry->get_value("memberUid");
1491                                                                                 foreach my $receiver (@receivers) { 
1492                                                                                                 $receiver_h{$1} = 0;
1493                                                                                 }
1494                                                                 }
1495                                                 } 
1496                                                 # translating errors ?
1497                                                 if ($mesg->code) {
1498                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1499                                                 }
1500                                 # ldap handle error ?           
1501                                 } else {
1502                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1503                                 }
1504                         } else {
1505                                 my $sbjct = &encode_base64(@{$hit}[1]);
1506                                 my $msg = &encode_base64(@{$hit}[7]);
1507                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1508                         }
1509                 }
1510                 my @receiver_l = keys(%receiver_h);
1512         my $message_id = @{$hit}[0];
1514         #add each outgoing msg to messaging_db
1515         my $receiver;
1516         foreach $receiver (@receiver_l) {
1517             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1518                 "VALUES ('".
1519                 $message_id."', '".    # id
1520                 @{$hit}[1]."', '".     # subject
1521                 @{$hit}[2]."', '".     # message_from
1522                 $receiver."', '".      # message_to
1523                 "none"."', '".         # flag
1524                 "out"."', '".          # direction
1525                 @{$hit}[6]."', '".     # delivery_time
1526                 @{$hit}[7]."', '".     # message
1527                 $timestamp."'".     # timestamp
1528                 ")";
1529             &daemon_log("M DEBUG: $sql_statement", 1);
1530             my $res = $messaging_db->exec_statement($sql_statement);
1531             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1532         }
1534         # set incoming message to flag d=deliverd
1535         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1536         &daemon_log("M DEBUG: $sql_statement", 7);
1537         $res = $messaging_db->update_dbentry($sql_statement);
1538         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1539     }
1541     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1542     return;
1545 sub watch_for_delivery_messages {
1546     my ($kernel, $heap) = @_[KERNEL, HEAP];
1548     # select outgoing messages
1549     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1550     #&daemon_log("0 DEBUG: $sql", 7);
1551     my $res = $messaging_db->exec_statement( $sql_statement );
1552     
1553     # build out msg for each    usr
1554     foreach my $hit (@{$res}) {
1555         my $receiver = @{$hit}[3];
1556         my $msg_id = @{$hit}[0];
1557         my $subject = @{$hit}[1];
1558         my $message = @{$hit}[7];
1560         # resolve usr -> host where usr is logged in
1561         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1562         #&daemon_log("0 DEBUG: $sql", 7);
1563         my $res = $login_users_db->exec_statement($sql);
1565         # reciver is logged in nowhere
1566         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1568                 my $send_succeed = 0;
1569                 foreach my $hit (@$res) {
1570                                 my $receiver_host = @$hit[0];
1571                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1573                                 # fetch key to encrypt msg propperly for usr/host
1574                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1575                                 &daemon_log("0 DEBUG: $sql", 7);
1576                                 my $res = $known_clients_db->exec_statement($sql);
1578                                 # host is already down
1579                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1581                                 # host is on
1582                                 my $receiver_key = @{@{$res}[0]}[2];
1583                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1584                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1585                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1586                                 if ($error == 0 ) {
1587                                         $send_succeed++ ;
1588                                 }
1589                 }
1591                 if ($send_succeed) {
1592                                 # set outgoing msg at db to deliverd
1593                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1594                                 &daemon_log("0 DEBUG: $sql", 7);
1595                                 my $res = $messaging_db->exec_statement($sql); 
1596                 }
1597         }
1599     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1600     return;
1604 sub watch_for_done_messages {
1605     my ($kernel,$heap) = @_[KERNEL, HEAP];
1607     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1608     #&daemon_log("0 DEBUG: $sql", 7);
1609     my $res = $messaging_db->exec_statement($sql); 
1611     foreach my $hit (@{$res}) {
1612         my $msg_id = @{$hit}[0];
1614         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1615         #&daemon_log("0 DEBUG: $sql", 7); 
1616         my $res = $messaging_db->exec_statement($sql);
1618         # not all usr msgs have been seen till now
1619         if ( ref(@$res[0]) eq "ARRAY") { next; }
1620         
1621         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1622         #&daemon_log("0 DEBUG: $sql", 7);
1623         $res = $messaging_db->exec_statement($sql);
1624     
1625     }
1627     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1628     return;
1632 sub get_ldap_handle {
1633         my ($session_id) = @_;
1634         my $heap;
1635         my $ldap_handle;
1637         if (not defined $session_id ) { $session_id = 0 };
1638         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1640         if ($session_id == 0) {
1641                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1642                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1643                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1645         } else {
1646                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1647                 if( defined $session_reference ) {
1648                         $heap = $session_reference->get_heap();
1649                 }
1651                 if (not defined $heap) {
1652                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1653                         return;
1654                 }
1656                 # TODO: This "if" is nonsense, because it doesn't prove that the
1657                 #       used handle is still valid - or if we've to reconnect...
1658                 #if (not exists $heap->{ldap_handle}) {
1659                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1660                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1661                         $heap->{ldap_handle} = $ldap_handle;
1662                 #}
1663         }
1664         return $ldap_handle;
1668 sub change_fai_state {
1669     my ($st, $targets, $session_id) = @_;
1670     $session_id = 0 if not defined $session_id;
1671     # Set FAI state to localboot
1672     my %mapActions= (
1673         reboot    => '',
1674         update    => 'softupdate',
1675         localboot => 'localboot',
1676         reinstall => 'install',
1677         rescan    => '',
1678         wake      => '',
1679         memcheck  => 'memcheck',
1680         sysinfo   => 'sysinfo',
1681         install   => 'install',
1682     );
1684     # Return if this is unknown
1685     if (!exists $mapActions{ $st }){
1686         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1687       return;
1688     }
1690     my $state= $mapActions{ $st };
1692     my $ldap_handle = &get_ldap_handle($session_id);
1693     if( defined($ldap_handle) ) {
1695       # Build search filter for hosts
1696         my $search= "(&(objectClass=GOhard)";
1697         foreach (@{$targets}){
1698             $search.= "(macAddress=$_)";
1699         }
1700         $search.= ")";
1702       # If there's any host inside of the search string, procress them
1703         if (!($search =~ /macAddress/)){
1704             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1705             return;
1706         }
1708       # Perform search for Unit Tag
1709       my $mesg = $ldap_handle->search(
1710           base   => $ldap_base,
1711           scope  => 'sub',
1712           attrs  => ['dn', 'FAIstate', 'objectClass'],
1713           filter => "$search"
1714           );
1716           if ($mesg->count) {
1717                   my @entries = $mesg->entries;
1718                   foreach my $entry (@entries) {
1719                           # Only modify entry if it is not set to '$state'
1720                           if ($entry->get_value("FAIstate") ne "$state"){
1721                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1722                                   my $result;
1723                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1724                                   if (exists $tmp{'FAIobject'}){
1725                                           if ($state eq ''){
1726                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1727                                                           delete => [ FAIstate => [] ] ]);
1728                                           } else {
1729                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1730                                                           replace => [ FAIstate => $state ] ]);
1731                                           }
1732                                   } elsif ($state ne ''){
1733                                           $result= $ldap_handle->modify($entry->dn, changes => [
1734                                                   add     => [ objectClass => 'FAIobject' ],
1735                                                   add     => [ FAIstate => $state ] ]);
1736                                   }
1738                                   # Errors?
1739                                   if ($result->code){
1740                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1741                                   }
1742                           } else {
1743                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1744                           }  
1745                   }
1746           }
1747     # if no ldap handle defined
1748     } else {
1749         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1750     }
1755 sub change_goto_state {
1756     my ($st, $targets, $session_id) = @_;
1757     $session_id = 0  if not defined $session_id;
1759     # Switch on or off?
1760     my $state= $st eq 'active' ? 'active': 'locked';
1762     my $ldap_handle = &get_ldap_handle($session_id);
1763     if( defined($ldap_handle) ) {
1765       # Build search filter for hosts
1766       my $search= "(&(objectClass=GOhard)";
1767       foreach (@{$targets}){
1768         $search.= "(macAddress=$_)";
1769       }
1770       $search.= ")";
1772       # If there's any host inside of the search string, procress them
1773       if (!($search =~ /macAddress/)){
1774         return;
1775       }
1777       # Perform search for Unit Tag
1778       my $mesg = $ldap_handle->search(
1779           base   => $ldap_base,
1780           scope  => 'sub',
1781           attrs  => ['dn', 'gotoMode'],
1782           filter => "$search"
1783           );
1785       if ($mesg->count) {
1786         my @entries = $mesg->entries;
1787         foreach my $entry (@entries) {
1789           # Only modify entry if it is not set to '$state'
1790           if ($entry->get_value("gotoMode") ne $state){
1792             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1793             my $result;
1794             $result= $ldap_handle->modify($entry->dn, changes => [
1795                                                 replace => [ gotoMode => $state ] ]);
1797             # Errors?
1798             if ($result->code){
1799               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1800             }
1802           }
1803         }
1804       }
1806     }
1810 sub run_create_fai_server_db {
1811     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1812     my $session_id = $session->ID;
1813     my $task = POE::Wheel::Run->new(
1814             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1815             StdoutEvent  => "session_run_result",
1816             StderrEvent  => "session_run_debug",
1817             CloseEvent   => "session_run_done",
1818             );
1820     $heap->{task}->{ $task->ID } = $task;
1821     return;
1825 sub create_fai_server_db {
1826     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1827         my $result;
1829         if (not defined $session_id) { $session_id = 0; }
1830     my $ldap_handle = &get_ldap_handle();
1831         if(defined($ldap_handle)) {
1832                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1833                 my $mesg= $ldap_handle->search(
1834                         base   => $ldap_base,
1835                         scope  => 'sub',
1836                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1837                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1838                 );
1839                 if($mesg->{'resultCode'} == 0 &&
1840                    $mesg->count != 0) {
1841                    foreach my $entry (@{$mesg->{entries}}) {
1842                            if($entry->exists('FAIrepository')) {
1843                                    # Add an entry for each Repository configured for server
1844                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1845                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1846                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1847                                                    $result= $fai_server_db->add_dbentry( { 
1848                                                                    table => $table_name,
1849                                                                    primkey => ['server', 'release', 'tag'],
1850                                                                    server => $tmp_url,
1851                                                                    release => $tmp_release,
1852                                                                    sections => $tmp_sections,
1853                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1854                                                            } );
1855                                            }
1856                                    }
1857                            }
1858                    }
1859                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1861                 # TODO: Find a way to post the 'create_packages_list_db' event
1862                 if(not defined($dont_create_packages_list)) {
1863                         &create_packages_list_db(undef, undef, $session_id);
1864                 }
1865         }       
1866     
1867     $ldap_handle->disconnect;
1868         return $result;
1872 sub run_create_fai_release_db {
1873     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1874         my $session_id = $session->ID;
1875     my $task = POE::Wheel::Run->new(
1876             Program => sub { &create_fai_release_db($table_name, $session_id) },
1877             StdoutEvent  => "session_run_result",
1878             StderrEvent  => "session_run_debug",
1879             CloseEvent   => "session_run_done",
1880             );
1882     $heap->{task}->{ $task->ID } = $task;
1883     return;
1887 sub create_fai_release_db {
1888         my ($table_name, $session_id) = @_;
1889         my $result;
1891     # used for logging
1892     if (not defined $session_id) { $session_id = 0; }
1894     my $ldap_handle = &get_ldap_handle();
1895         if(defined($ldap_handle)) {
1896                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1897                 my $mesg= $ldap_handle->search(
1898                         base   => $ldap_base,
1899                         scope  => 'sub',
1900                         attrs  => [],
1901                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1902                 );
1903                 if($mesg->{'resultCode'} == 0 &&
1904                         $mesg->count != 0) {
1905                         # Walk through all possible FAI container ou's
1906                         my @sql_list;
1907                         my $timestamp= &get_time();
1908                         foreach my $ou (@{$mesg->{entries}}) {
1909                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1910                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1911                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1912                                         if(@tmp_array) {
1913                                                 foreach my $entry (@tmp_array) {
1914                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1915                                                                 my $sql= 
1916                                                                 "INSERT INTO $table_name "
1917                                                                 ."(timestamp, release, class, type, state) VALUES ("
1918                                                                 .$timestamp.","
1919                                                                 ."'".$entry->{'release'}."',"
1920                                                                 ."'".$entry->{'class'}."',"
1921                                                                 ."'".$entry->{'type'}."',"
1922                                                                 ."'".$entry->{'state'}."')";
1923                                                                 push @sql_list, $sql;
1924                                                         }
1925                                                 }
1926                                         }
1927                                 }
1928                         }
1930                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1931                         if(@sql_list) {
1932                                 unshift @sql_list, "VACUUM";
1933                                 unshift @sql_list, "DELETE FROM $table_name";
1934                                 $fai_release_db->exec_statementlist(\@sql_list);
1935                         }
1936                         daemon_log("$session_id DEBUG: Done with inserting",7);
1937                 }
1938                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1939         }
1940     $ldap_handle->disconnect;
1941         return $result;
1944 sub get_fai_types {
1945         my $tmp_classes = shift || return undef;
1946         my @result;
1948         foreach my $type(keys %{$tmp_classes}) {
1949                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1950                         my $entry = {
1951                                 type => $type,
1952                                 state => $tmp_classes->{$type}[0],
1953                         };
1954                         push @result, $entry;
1955                 }
1956         }
1958         return @result;
1961 sub get_fai_state {
1962         my $result = "";
1963         my $tmp_classes = shift || return $result;
1965         foreach my $type(keys %{$tmp_classes}) {
1966                 if(defined($tmp_classes->{$type}[0])) {
1967                         $result = $tmp_classes->{$type}[0];
1968                         
1969                 # State is equal for all types in class
1970                         last;
1971                 }
1972         }
1974         return $result;
1977 sub resolve_fai_classes {
1978         my ($fai_base, $ldap_handle, $session_id) = @_;
1979         if (not defined $session_id) { $session_id = 0; }
1980         my $result;
1981         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1982         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1983         my $fai_classes;
1985         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1986         my $mesg= $ldap_handle->search(
1987                 base   => $fai_base,
1988                 scope  => 'sub',
1989                 attrs  => ['cn','objectClass','FAIstate'],
1990                 filter => $fai_filter,
1991         );
1992         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1994         if($mesg->{'resultCode'} == 0 &&
1995                 $mesg->count != 0) {
1996                 foreach my $entry (@{$mesg->{entries}}) {
1997                         if($entry->exists('cn')) {
1998                                 my $tmp_dn= $entry->dn();
2000                                 # Skip classname and ou dn parts for class
2001                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2003                                 # Skip classes without releases
2004                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2005                                         next;
2006                                 }
2008                                 my $tmp_cn= $entry->get_value('cn');
2009                                 my $tmp_state= $entry->get_value('FAIstate');
2011                                 my $tmp_type;
2012                                 # Get FAI type
2013                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2014                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2015                                                 $tmp_type= $oclass;
2016                                                 last;
2017                                         }
2018                                 }
2020                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2021                                         # A Subrelease
2022                                         my @sub_releases = split(/,/, $tmp_release);
2024                                         # Walk through subreleases and build hash tree
2025                                         my $hash;
2026                                         while(my $tmp_sub_release = pop @sub_releases) {
2027                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2028                                         }
2029                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2030                                 } else {
2031                                         # A branch, no subrelease
2032                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2033                                 }
2034                         } elsif (!$entry->exists('cn')) {
2035                                 my $tmp_dn= $entry->dn();
2036                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2038                                 # Skip classes without releases
2039                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2040                                         next;
2041                                 }
2043                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2044                                         # A Subrelease
2045                                         my @sub_releases= split(/,/, $tmp_release);
2047                                         # Walk through subreleases and build hash tree
2048                                         my $hash;
2049                                         while(my $tmp_sub_release = pop @sub_releases) {
2050                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2051                                         }
2052                                         # Remove the last two characters
2053                                         chop($hash);
2054                                         chop($hash);
2056                                         eval('$fai_classes->'.$hash.'= {}');
2057                                 } else {
2058                                         # A branch, no subrelease
2059                                         if(!exists($fai_classes->{$tmp_release})) {
2060                                                 $fai_classes->{$tmp_release} = {};
2061                                         }
2062                                 }
2063                         }
2064                 }
2066                 # The hash is complete, now we can honor the copy-on-write based missing entries
2067                 foreach my $release (keys %$fai_classes) {
2068                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2069                 }
2070         }
2071         return $result;
2074 sub apply_fai_inheritance {
2075        my $fai_classes = shift || return {};
2076        my $tmp_classes;
2078        # Get the classes from the branch
2079        foreach my $class (keys %{$fai_classes}) {
2080                # Skip subreleases
2081                if($class =~ /^ou=.*$/) {
2082                        next;
2083                } else {
2084                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2085                }
2086        }
2088        # Apply to each subrelease
2089        foreach my $subrelease (keys %{$fai_classes}) {
2090                if($subrelease =~ /ou=/) {
2091                        foreach my $tmp_class (keys %{$tmp_classes}) {
2092                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2093                                        $fai_classes->{$subrelease}->{$tmp_class} =
2094                                        deep_copy($tmp_classes->{$tmp_class});
2095                                } else {
2096                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2097                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2098                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2099                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2100                                                }
2101                                        }
2102                                }
2103                        }
2104                }
2105        }
2107        # Find subreleases in deeper levels
2108        foreach my $subrelease (keys %{$fai_classes}) {
2109                if($subrelease =~ /ou=/) {
2110                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2111                                if($subsubrelease =~ /ou=/) {
2112                                        apply_fai_inheritance($fai_classes->{$subrelease});
2113                                }
2114                        }
2115                }
2116        }
2118        return $fai_classes;
2121 sub get_fai_release_entries {
2122         my $tmp_classes = shift || return;
2123         my $parent = shift || "";
2124         my @result = shift || ();
2126         foreach my $entry (keys %{$tmp_classes}) {
2127                 if(defined($entry)) {
2128                         if($entry =~ /^ou=.*$/) {
2129                                 my $release_name = $entry;
2130                                 $release_name =~ s/ou=//g;
2131                                 if(length($parent)>0) {
2132                                         $release_name = $parent."/".$release_name;
2133                                 }
2134                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2135                                 foreach my $bufentry(@bufentries) {
2136                                         push @result, $bufentry;
2137                                 }
2138                         } else {
2139                                 my @types = get_fai_types($tmp_classes->{$entry});
2140                                 foreach my $type (@types) {
2141                                         push @result, 
2142                                         {
2143                                                 'class' => $entry,
2144                                                 'type' => $type->{'type'},
2145                                                 'release' => $parent,
2146                                                 'state' => $type->{'state'},
2147                                         };
2148                                 }
2149                         }
2150                 }
2151         }
2153         return @result;
2156 sub deep_copy {
2157         my $this = shift;
2158         if (not ref $this) {
2159                 $this;
2160         } elsif (ref $this eq "ARRAY") {
2161                 [map deep_copy($_), @$this];
2162         } elsif (ref $this eq "HASH") {
2163                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2164         } else { die "what type is $_?" }
2168 sub session_run_result {
2169     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2170     $kernel->sig(CHLD => "child_reap");
2173 sub session_run_debug {
2174     my $result = $_[ARG0];
2175     print STDERR "$result\n";
2178 sub session_run_done {
2179     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2180     delete $heap->{task}->{$task_id};
2184 sub create_sources_list {
2185         my $session_id = shift;
2186         my $ldap_handle = &main::get_ldap_handle;
2187         my $result="/tmp/gosa_si_tmp_sources_list";
2189         # Remove old file
2190         if(stat($result)) {
2191                 unlink($result);
2192                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2193         }
2195         my $fh;
2196         open($fh, ">$result");
2197         if (not defined $fh) {
2198                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2199                 return undef;
2200         }
2201         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2202                 my $mesg=$ldap_handle->search(
2203                         base    => $main::ldap_server_dn,
2204                         scope   => 'base',
2205                         attrs   => 'FAIrepository',
2206                         filter  => 'objectClass=FAIrepositoryServer'
2207                 );
2208                 if($mesg->count) {
2209                         foreach my $entry(@{$mesg->{'entries'}}) {
2210                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2211                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2212                                         my $line = "deb $server $release";
2213                                         $sections =~ s/,/ /g;
2214                                         $line.= " $sections";
2215                                         print $fh $line."\n";
2216                                 }
2217                         }
2218                 }
2219         } else {
2220                 if (defined $main::ldap_server_dn){
2221                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2222                 } else {
2223                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2224                 }
2225         }
2226         close($fh);
2228         return $result;
2232 sub run_create_packages_list_db {
2233     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2234         my $session_id = $session->ID;
2236         my $task = POE::Wheel::Run->new(
2237                                         Priority => +20,
2238                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2239                                         StdoutEvent  => "session_run_result",
2240                                         StderrEvent  => "session_run_debug",
2241                                         CloseEvent   => "session_run_done",
2242                                         );
2243         $heap->{task}->{ $task->ID } = $task;
2247 sub create_packages_list_db {
2248         my ($ldap_handle, $sources_file, $session_id) = @_;
2249         
2250         # it should not be possible to trigger a recreation of packages_list_db
2251         # while packages_list_db is under construction, so set flag packages_list_under_construction
2252         # which is tested befor recreation can be started
2253         if (-r $packages_list_under_construction) {
2254                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2255                 return;
2256         } else {
2257                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2258                 # set packages_list_under_construction to true
2259                 system("touch $packages_list_under_construction");
2260                 @packages_list_statements=();
2261         }
2263         if (not defined $session_id) { $session_id = 0; }
2264         if (not defined $ldap_handle) { 
2265                 $ldap_handle= &get_ldap_handle();
2267                 if (not defined $ldap_handle) {
2268                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2269                         unlink($packages_list_under_construction);
2270                         return;
2271                 }
2272         }
2273         if (not defined $sources_file) { 
2274                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2275                 $sources_file = &create_sources_list($session_id);
2276         }
2278         if (not defined $sources_file) {
2279                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2280                 unlink($packages_list_under_construction);
2281                 return;
2282         }
2284         my $line;
2286         open(CONFIG, "<$sources_file") or do {
2287                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2288                 unlink($packages_list_under_construction);
2289                 return;
2290         };
2292         # Read lines
2293         while ($line = <CONFIG>){
2294                 # Unify
2295                 chop($line);
2296                 $line =~ s/^\s+//;
2297                 $line =~ s/^\s+/ /;
2299                 # Strip comments
2300                 $line =~ s/#.*$//g;
2302                 # Skip empty lines
2303                 if ($line =~ /^\s*$/){
2304                         next;
2305                 }
2307                 # Interpret deb line
2308                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2309                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2310                         my $section;
2311                         foreach $section (split(' ', $sections)){
2312                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2313                         }
2314                 }
2315         }
2317         close (CONFIG);
2319         find(\&cleanup_and_extract, keys( %repo_dirs ));
2320         &main::strip_packages_list_statements();
2321         unshift @packages_list_statements, "VACUUM";
2322         $packages_list_db->exec_statementlist(\@packages_list_statements);
2323         unlink($packages_list_under_construction);
2324         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2325         return;
2328 # This function should do some intensive task to minimize the db-traffic
2329 sub strip_packages_list_statements {
2330     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2331         my @new_statement_list=();
2332         my $hash;
2333         my $insert_hash;
2334         my $update_hash;
2335         my $delete_hash;
2336         my $local_timestamp=get_time();
2338         foreach my $existing_entry (@existing_entries) {
2339                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2340         }
2342         foreach my $statement (@packages_list_statements) {
2343                 if($statement =~ /^INSERT/i) {
2344                         # Assign the values from the insert statement
2345                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2346                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2347                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2348                                 # If section or description has changed, update the DB
2349                                 if( 
2350                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2351                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2352                                 ) {
2353                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2354                                 }
2355                         } else {
2356                                 # Insert a non-existing entry to db
2357                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2358                         }
2359                 } elsif ($statement =~ /^UPDATE/i) {
2360                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2361                         /^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;
2362                         foreach my $distribution (keys %{$hash}) {
2363                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2364                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2365                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2366                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2367                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2368                                                 my $section;
2369                                                 my $description;
2370                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2371                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2372                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2373                                                 }
2374                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2375                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2376                                                 }
2377                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2378                                         }
2379                                 }
2380                         }
2381                 }
2382         }
2384         # TODO: Check for orphaned entries
2386         # unroll the insert_hash
2387         foreach my $distribution (keys %{$insert_hash}) {
2388                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2389                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2390                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2391                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2392                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2393                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2394                                 ."'$local_timestamp')";
2395                         }
2396                 }
2397         }
2399         # unroll the update hash
2400         foreach my $distribution (keys %{$update_hash}) {
2401                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2402                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2403                                 my $set = "";
2404                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2405                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2406                                 }
2407                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2408                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2409                                 }
2410                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2411                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2412                                 }
2413                                 if(defined($set) and length($set) > 0) {
2414                                         $set .= "timestamp = '$local_timestamp'";
2415                                 } else {
2416                                         next;
2417                                 }
2418                                 push @new_statement_list, 
2419                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2420                                         ." distribution = '$distribution'"
2421                                         ." AND package = '$package'"
2422                                         ." AND version = '$version'";
2423                         }
2424                 }
2425         }
2427         @packages_list_statements = @new_statement_list;
2431 sub parse_package_info {
2432     my ($baseurl, $dist, $section, $session_id)= @_;
2433     my ($package);
2434     if (not defined $session_id) { $session_id = 0; }
2435     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2436     $repo_dirs{ "${repo_path}/pool" } = 1;
2438     foreach $package ("Packages.gz"){
2439         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2440         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2441         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2442     }
2443     
2447 sub get_package {
2448     my ($url, $dest, $session_id)= @_;
2449     if (not defined $session_id) { $session_id = 0; }
2451     my $tpath = dirname($dest);
2452     -d "$tpath" || mkpath "$tpath";
2454     # This is ugly, but I've no time to take a look at "how it works in perl"
2455     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2456         system("gunzip -cd '$dest' > '$dest.in'");
2457         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2458         unlink($dest);
2459         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2460     } else {
2461         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2462     }
2463     return 0;
2467 sub parse_package {
2468     my ($path, $dist, $srv_path, $session_id)= @_;
2469     if (not defined $session_id) { $session_id = 0;}
2470     my ($package, $version, $section, $description);
2471     my $PACKAGES;
2472     my $timestamp = &get_time();
2474     if(not stat("$path.in")) {
2475         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2476         return;
2477     }
2479     open($PACKAGES, "<$path.in");
2480     if(not defined($PACKAGES)) {
2481         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2482         return;
2483     }
2485     # Read lines
2486     while (<$PACKAGES>){
2487         my $line = $_;
2488         # Unify
2489         chop($line);
2491         # Use empty lines as a trigger
2492         if ($line =~ /^\s*$/){
2493             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2494             push(@packages_list_statements, $sql);
2495             $package = "none";
2496             $version = "none";
2497             $section = "none";
2498             $description = "none"; 
2499             next;
2500         }
2502         # Trigger for package name
2503         if ($line =~ /^Package:\s/){
2504             ($package)= ($line =~ /^Package: (.*)$/);
2505             next;
2506         }
2508         # Trigger for version
2509         if ($line =~ /^Version:\s/){
2510             ($version)= ($line =~ /^Version: (.*)$/);
2511             next;
2512         }
2514         # Trigger for description
2515         if ($line =~ /^Description:\s/){
2516             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2517             next;
2518         }
2520         # Trigger for section
2521         if ($line =~ /^Section:\s/){
2522             ($section)= ($line =~ /^Section: (.*)$/);
2523             next;
2524         }
2526         # Trigger for filename
2527         if ($line =~ /^Filename:\s/){
2528             my ($filename) = ($line =~ /^Filename: (.*)$/);
2529             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2530             next;
2531         }
2532     }
2534     close( $PACKAGES );
2535     unlink( "$path.in" );
2536     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2540 sub store_fileinfo {
2541     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2543     my %fileinfo = (
2544         'package' => $package,
2545         'dist' => $dist,
2546         'version' => $vers,
2547     );
2549     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2553 sub cleanup_and_extract {
2554     my $fileinfo = $repo_files{ $File::Find::name };
2556     if( defined $fileinfo ) {
2558         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2559         my $sql;
2560         my $package = $fileinfo->{ 'package' };
2561         my $newver = $fileinfo->{ 'version' };
2563         mkpath($dir);
2564         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2566                 if( -f "$dir/DEBIAN/templates" ) {
2568                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2570                         my $tmpl= "";
2571                         {
2572                                 local $/=undef;
2573                                 open FILE, "$dir/DEBIAN/templates";
2574                                 $tmpl = &encode_base64(<FILE>);
2575                                 close FILE;
2576                         }
2577                         rmtree("$dir/DEBIAN/templates");
2579                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2580                 push @packages_list_statements, $sql;
2581                 }
2582     }
2584     return;
2588 sub register_at_foreign_servers {   
2589     my ($kernel) = $_[KERNEL];
2591     # hole alle bekannten server aus known_server_db
2592     my $server_sql = "SELECT * FROM $known_server_tn";
2593     my $server_res = $known_server_db->exec_statement($server_sql);
2595     # no entries in known_server_db
2596     if (not ref(@$server_res[0]) eq "ARRAY") { 
2597         # TODO
2598     }
2600     # detect already connected clients
2601     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2602     my $client_res = $known_clients_db->exec_statement($client_sql);
2604     # send my server details to all other gosa-si-server within the network
2605     foreach my $hit (@$server_res) {
2606         my $hostname = @$hit[0];
2607         my $hostkey = &create_passwd;
2609         # add already connected clients to registration message 
2610         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2611         &add_content2xml_hash($myhash, 'key', $hostkey);
2612         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2613         
2614         # build registration message and send it
2615         my $foreign_server_msg = &create_xml_string($myhash);
2616         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2617     }
2618     
2619     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2620     return;
2624 #==== MAIN = main ==============================================================
2625 #  parse commandline options
2626 Getopt::Long::Configure( "bundling" );
2627 GetOptions("h|help" => \&usage,
2628         "c|config=s" => \$cfg_file,
2629         "f|foreground" => \$foreground,
2630         "v|verbose+" => \$verbose,
2631         "no-bus+" => \$no_bus,
2632         "no-arp+" => \$no_arp,
2633            );
2635 #  read and set config parameters
2636 &check_cmdline_param ;
2637 &read_configfile;
2638 &check_pid;
2640 $SIG{CHLD} = 'IGNORE';
2642 # forward error messages to logfile
2643 if( ! $foreground ) {
2644   open( STDIN,  '+>/dev/null' );
2645   open( STDOUT, '+>&STDIN'    );
2646   open( STDERR, '+>&STDIN'    );
2649 # Just fork, if we are not in foreground mode
2650 if( ! $foreground ) { 
2651     chdir '/'                 or die "Can't chdir to /: $!";
2652     $pid = fork;
2653     setsid                    or die "Can't start a new session: $!";
2654     umask 0;
2655 } else { 
2656     $pid = $$; 
2659 # Do something useful - put our PID into the pid_file
2660 if( 0 != $pid ) {
2661     open( LOCK_FILE, ">$pid_file" );
2662     print LOCK_FILE "$pid\n";
2663     close( LOCK_FILE );
2664     if( !$foreground ) { 
2665         exit( 0 ) 
2666     };
2669 # parse head url and revision from svn
2670 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2671 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2672 $server_headURL = defined $1 ? $1 : 'unknown' ;
2673 $server_revision = defined $2 ? $2 : 'unknown' ;
2674 if ($server_headURL =~ /\/tag\// || 
2675         $server_headURL =~ /\/branches\// ) {
2676     $server_status = "stable"; 
2677 } else {
2678     $server_status = "developmental" ;
2682 daemon_log(" ", 1);
2683 daemon_log("$0 started!", 1);
2684 daemon_log("status: $server_status", 1);
2685 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2687 if ($no_bus > 0) {
2688     $bus_activ = "false"
2691 # connect to incoming_db
2692 unlink($incoming_file_name);
2693 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2694 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2696 # connect to gosa-si job queue
2697 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2698 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2700 # connect to known_clients_db
2701 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2702 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2704 # connect to foreign_clients_db
2705 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2706 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2708 # connect to known_server_db
2709 unlink($known_server_file_name);
2710 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2711 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2713 # connect to login_usr_db
2714 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2715 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2717 # connect to fai_server_db and fai_release_db
2718 unlink($fai_server_file_name);
2719 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2720 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2722 unlink($fai_release_file_name);
2723 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2724 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2726 # connect to packages_list_db
2727 #unlink($packages_list_file_name);
2728 unlink($packages_list_under_construction);
2729 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2730 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2732 # connect to messaging_db
2733 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2734 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2737 # create xml object used for en/decrypting
2738 $xml = new XML::Simple();
2741 # foreign servers 
2742 my @foreign_server_list;
2744 # add foreign server from cfg file
2745 if ($foreign_server_string ne "") {
2746     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2747     foreach my $foreign_server (@cfg_foreign_server_list) {
2748         push(@foreign_server_list, $foreign_server);
2749     }
2752 # add foreign server from dns
2753 my @tmp_servers;
2754 if ( !$server_domain) {
2755     # Try our DNS Searchlist
2756     for my $domain(get_dns_domains()) {
2757         chomp($domain);
2758         my @tmp_domains= &get_server_addresses($domain);
2759         if(@tmp_domains) {
2760             for my $tmp_server(@tmp_domains) {
2761                 push @tmp_servers, $tmp_server;
2762             }
2763         }
2764     }
2765     if(@tmp_servers && length(@tmp_servers)==0) {
2766         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2767     }
2768 } else {
2769     @tmp_servers = &get_server_addresses($server_domain);
2770     if( 0 == @tmp_servers ) {
2771         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2772     }
2774 foreach my $server (@tmp_servers) { 
2775     unshift(@foreign_server_list, $server); 
2777 # eliminate duplicate entries
2778 @foreign_server_list = &del_doubles(@foreign_server_list);
2779 my $all_foreign_server = join(", ", @foreign_server_list);
2780 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2782 # add all found foreign servers to known_server
2783 my $act_timestamp = &get_time();
2784 foreach my $foreign_server (@foreign_server_list) {
2785     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2786             primkey=>['hostname'],
2787             hostname=>$foreign_server,
2788             status=>'not_jet_registered',
2789             hostkey=>"none",
2790             timestamp=>$act_timestamp,
2791             } );
2795 POE::Component::Server::TCP->new(
2796         Port => $server_port,
2797         ClientInput => sub {
2798         my ($kernel, $input) = @_[KERNEL, ARG0];
2799         push(@tasks, $input);
2800         push(@msgs_to_decrypt, $input);
2801         $kernel->yield("msg_to_decrypt");
2802         $kernel->yield("next_task");
2803         },
2804     InlineStates => {
2805         next_task => \&next_task,
2806         msg_to_decrypt => \&msg_to_decrypt,
2807         task_result => \&handle_task_result,
2808         task_done   => \&handle_task_done,
2809         task_debug  => \&handle_task_debug,
2810         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2811     }
2812 );
2814 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2816 # create session for repeatedly checking the job queue for jobs
2817 POE::Session->create(
2818         inline_states => {
2819                 _start => \&_start,
2820         register_at_foreign_servers => \&register_at_foreign_servers,
2821                 sig_handler => \&sig_handler,
2822         watch_for_new_messages => \&watch_for_new_messages,
2823         watch_for_delivery_messages => \&watch_for_delivery_messages,
2824         watch_for_done_messages => \&watch_for_done_messages,
2825                 watch_for_new_jobs => \&watch_for_new_jobs,
2826         watch_for_done_jobs => \&watch_for_done_jobs,
2827         create_packages_list_db => \&run_create_packages_list_db,
2828         create_fai_server_db => \&run_create_fai_server_db,
2829         create_fai_release_db => \&run_create_fai_release_db,
2830         session_run_result => \&session_run_result,
2831         session_run_debug => \&session_run_debug,
2832         session_run_done => \&session_run_done,
2833         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2834         }
2835 );
2838 # import all modules
2839 &import_modules;
2841 # TODO
2842 # check wether all modules are gosa-si valid passwd check
2846 POE::Kernel->run();
2847 exit;