Code

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