Code

* group to user translation for message delivery
[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);
57 my $modules_path = "/usr/lib/gosa-si/modules";
58 use lib "/usr/lib/gosa-si/modules";
60 # revision number of server and program name
61 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
62 my $server_headURL;
63 my $server_revision;
64 my $server_status;
65 our $prg= basename($0);
67 our $global_kernel;
68 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
69 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($known_modules);
74 my ($pid_file, $procid, $pid, $log_file);
75 my ($arp_activ, $arp_fifo);
76 my ($xml);
77 my $sources_list;
78 my $max_clients;
79 my %repo_files=();
80 my $repo_path;
81 my %repo_dirs=();
82 # variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $SIPackages_key, 
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
87 );
89 # additional variable which should be globaly accessable
90 our $server_address;
91 our $server_mac_address;
92 our $bus_address;
93 our $gosa_address;
94 our $no_bus;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # specifies the verbosity of the daemon_log
103 $verbose = 0 ;
105 # if foreground is not null, script will be not forked to background
106 $foreground = 0 ;
108 # specifies the timeout seconds while checking the online status of a registrating client
109 $ping_timeout = 5;
111 $no_bus = 0;
112 $bus_activ = "true";
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
123         "timestamp DEFAULT 'none'", 
124         "headertag DEFAULT 'none'",
125                 "targettag DEFAULT 'none'",
126         "xmlmessage DEFAULT 'none'",
127         "module DEFAULT 'none'",
128         );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
135                 "timestamp DEFAULT 'none'", 
136                 "status DEFAULT 'none'", 
137                 "result DEFAULT 'none'", 
138                 "progress DEFAULT 'none'", 
139         "headertag DEFAULT 'none'", 
140                 "targettag DEFAULT 'none'", 
141                 "xmlmessage DEFAULT 'none'", 
142                 "macaddress DEFAULT 'none'",
143                 "plainname DEFAULT 'none'",
144                 );
146 # holds all other gosa-sd as well as the gosa-sd-bus
147 our $known_server_db;
148 our $known_server_tn = "known_server";
149 my $known_server_file_name;
150 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
152 # holds all registrated clients
153 our $known_clients_db;
154 our $known_clients_tn = "known_clients";
155 my $known_clients_file_name;
156 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
158 # holds all logged in user at each client 
159 our $login_users_db;
160 our $login_users_tn = "login_users";
161 my $login_users_file_name;
162 my @login_users_col_names = ("client", "user", "timestamp");
164 # holds all fai server, the debian release and tag
165 our $fai_server_db;
166 our $fai_server_tn = "fai_server"; 
167 my $fai_server_file_name;
168 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
170 our $fai_release_db;
171 our $fai_release_tn = "fai_release"; 
172 my $fai_release_file_name;
173 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
175 # holds all packages available from different repositories
176 our $packages_list_db;
177 our $packages_list_tn = "packages_list";
178 my $packages_list_file_name;
179 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
180 my $outdir = "/tmp/packages_list_db";
181 my $arch = "i386"; 
183 # holds all messages which should be delivered to a user
184 our $messaging_db;
185 our $messaging_tn = "messaging"; 
186 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
187         "flag", "direction", "delivery_time", "message", "timestamp" );
188 my $messaging_file_name;
190 # path to directory to store client install log files
191 our $client_fai_log_dir = "/var/log/fai"; 
193 # queue which stores taskes until one of the $max_children children are ready to process the task
194 my @tasks = qw();
195 my @msgs_to_decrypt = qw();
196 my $max_children = 2;
199 %cfg_defaults = (
200 "general" => {
201     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
202     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
203     },
204 "bus" => {
205     "activ" => [\$bus_activ, "true"],
206     },
207 "server" => {
208     "port" => [\$server_port, "20081"],
209     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
210     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
211     "incoming"      => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
212     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
213     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
214     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
215     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
216     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
217     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
218     "repo-path" => [\$repo_path, '/srv/www/repository'],
219     "ldap-uri" => [\$ldap_uri, ""],
220     "ldap-base" => [\$ldap_base, ""],
221     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
222     "ldap-admin-password" => [\$ldap_admin_password, ""],
223     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
224     "max-clients" => [\$max_clients, 10],
225     },
226 "GOsaPackages" => {
227     "ip" => [\$gosa_ip, "0.0.0.0"],
228     "port" => [\$gosa_port, "20082"],
229     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
230     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
231     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
232     "key" => [\$GosaPackages_key, "none"],
233     },
234 "SIPackages" => {
235     "key" => [\$SIPackages_key, "none"],
236     },
237 );
240 #===  FUNCTION  ================================================================
241 #         NAME:  usage
242 #   PARAMETERS:  nothing
243 #      RETURNS:  nothing
244 #  DESCRIPTION:  print out usage text to STDERR
245 #===============================================================================
246 sub usage {
247     print STDERR << "EOF" ;
248 usage: $prg [-hvf] [-c config]
250            -h        : this (help) message
251            -c <file> : config file
252            -f        : foreground, process will not be forked to background
253            -v        : be verbose (multiple to increase verbosity)
254            -no-bus   : starts $prg without connection to bus
255            -no-arp   : starts $prg without connection to arp module
256  
257 EOF
258     print "\n" ;
262 #===  FUNCTION  ================================================================
263 #         NAME:  read_configfile
264 #   PARAMETERS:  cfg_file - string -
265 #      RETURNS:  nothing
266 #  DESCRIPTION:  read cfg_file and set variables
267 #===============================================================================
268 sub read_configfile {
269     my $cfg;
270     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
271         if( -r $cfg_file ) {
272             $cfg = Config::IniFiles->new( -file => $cfg_file );
273         } else {
274             print STDERR "Couldn't read config file!\n";
275         }
276     } else {
277         $cfg = Config::IniFiles->new() ;
278     }
279     foreach my $section (keys %cfg_defaults) {
280         foreach my $param (keys %{$cfg_defaults{ $section }}) {
281             my $pinfo = $cfg_defaults{ $section }{ $param };
282             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
283         }
284     }
288 #===  FUNCTION  ================================================================
289 #         NAME:  logging
290 #   PARAMETERS:  level - string - default 'info'
291 #                msg - string -
292 #                facility - string - default 'LOG_DAEMON'
293 #      RETURNS:  nothing
294 #  DESCRIPTION:  function for logging
295 #===============================================================================
296 sub daemon_log {
297     # log into log_file
298     my( $msg, $level ) = @_;
299     if(not defined $msg) { return }
300     if(not defined $level) { $level = 1 }
301     if(defined $log_file){
302         open(LOG_HANDLE, ">>$log_file");
303         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
304             print STDERR "cannot open $log_file: $!";
305             return }
306             chomp($msg);
307                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
308             if($level <= $verbose){
309                 my ($seconds, $minutes, $hours, $monthday, $month,
310                         $year, $weekday, $yearday, $sommertime) = localtime(time);
311                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
312                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
313                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
314                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
315                 $month = $monthnames[$month];
316                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
317                 $year+=1900;
318                 my $name = $prg;
320                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
321                 print LOG_HANDLE $log_msg;
322                 if( $foreground ) { 
323                     print STDERR $log_msg;
324                 }
325             }
326         close( LOG_HANDLE );
327     }
331 #===  FUNCTION  ================================================================
332 #         NAME:  check_cmdline_param
333 #   PARAMETERS:  nothing
334 #      RETURNS:  nothing
335 #  DESCRIPTION:  validates commandline parameter
336 #===============================================================================
337 sub check_cmdline_param () {
338     my $err_config;
339     my $err_counter = 0;
340         if(not defined($cfg_file)) {
341                 $cfg_file = "/etc/gosa-si/server.conf";
342                 if(! -r $cfg_file) {
343                         $err_config = "please specify a config file";
344                         $err_counter += 1;
345                 }
346     }
347     if( $err_counter > 0 ) {
348         &usage( "", 1 );
349         if( defined( $err_config)) { print STDERR "$err_config\n"}
350         print STDERR "\n";
351         exit( -1 );
352     }
356 #===  FUNCTION  ================================================================
357 #         NAME:  check_pid
358 #   PARAMETERS:  nothing
359 #      RETURNS:  nothing
360 #  DESCRIPTION:  handels pid processing
361 #===============================================================================
362 sub check_pid {
363     $pid = -1;
364     # Check, if we are already running
365     if( open(LOCK_FILE, "<$pid_file") ) {
366         $pid = <LOCK_FILE>;
367         if( defined $pid ) {
368             chomp( $pid );
369             if( -f "/proc/$pid/stat" ) {
370                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
371                 if( $stat ) {
372                                         daemon_log("ERROR: Already running",1);
373                     close( LOCK_FILE );
374                     exit -1;
375                 }
376             }
377         }
378         close( LOCK_FILE );
379         unlink( $pid_file );
380     }
382     # create a syslog msg if it is not to possible to open PID file
383     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
384         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
385         if (open(LOCK_FILE, '<', $pid_file)
386                 && ($pid = <LOCK_FILE>))
387         {
388             chomp($pid);
389             $msg .= "(PID $pid)\n";
390         } else {
391             $msg .= "(unable to read PID)\n";
392         }
393         if( ! ($foreground) ) {
394             openlog( $0, "cons,pid", "daemon" );
395             syslog( "warning", $msg );
396             closelog();
397         }
398         else {
399             print( STDERR " $msg " );
400         }
401         exit( -1 );
402     }
405 #===  FUNCTION  ================================================================
406 #         NAME:  import_modules
407 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
408 #                are stored
409 #      RETURNS:  nothing
410 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
411 #                state is on is imported by "require 'file';"
412 #===============================================================================
413 sub import_modules {
414     daemon_log(" ", 1);
416     if (not -e $modules_path) {
417         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
418     }
420     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
421     while (defined (my $file = readdir (DIR))) {
422         if (not $file =~ /(\S*?).pm$/) {
423             next;
424         }
425                 my $mod_name = $1;
427         if( $file =~ /ArpHandler.pm/ ) {
428             if( $no_arp > 0 ) {
429                 next;
430             }
431         }
432         
433         eval { require $file; };
434         if ($@) {
435             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
436             daemon_log("$@", 5);
437                 } else {
438                         my $info = eval($mod_name.'::get_module_info()');
439                         # Only load module if get_module_info() returns a non-null object
440                         if( $info ) {
441                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
442                                 $known_modules->{$mod_name} = $info;
443                                 daemon_log("INFO: module $mod_name loaded", 5);
444                         }
445                 }
446     }   
447     close (DIR);
451 #===  FUNCTION  ================================================================
452 #         NAME:  sig_int_handler
453 #   PARAMETERS:  signal - string - signal arose from system
454 #      RETURNS:  noting
455 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
456 #===============================================================================
457 sub sig_int_handler {
458     my ($signal) = @_;
460 #       if (defined($ldap_handle)) {
461 #               $ldap_handle->disconnect;
462 #       }
463     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
464     
466     daemon_log("shutting down gosa-si-server", 1);
467     system("kill `ps -C gosa-si-server -o pid=`");
469 $SIG{INT} = \&sig_int_handler;
472 sub check_key_and_xml_validity {
473     my ($crypted_msg, $module_key, $session_id) = @_;
474     my $msg;
475     my $msg_hash;
476     my $error_string;
477     eval{
478         $msg = &decrypt_msg($crypted_msg, $module_key);
480         if ($msg =~ /<xml>/i){
481             $msg =~ s/\s+/ /g;  # just for better daemon_log
482             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
483             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
485             ##############
486             # check header
487             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
488             my $header_l = $msg_hash->{'header'};
489             if( 1 > @{$header_l} ) { die 'empty header tag'; }
490             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
491             my $header = @{$header_l}[0];
492             if( 0 == length $header) { die 'empty string in header tag'; }
494             ##############
495             # check source
496             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
497             my $source_l = $msg_hash->{'source'};
498             if( 1 > @{$source_l} ) { die 'empty source tag'; }
499             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
500             my $source = @{$source_l}[0];
501             if( 0 == length $source) { die 'source error'; }
503             ##############
504             # check target
505             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
506             my $target_l = $msg_hash->{'target'};
507             if( 1 > @{$target_l} ) { die 'empty target tag'; }
508         }
509     };
510     if($@) {
511         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
512         $msg = undef;
513         $msg_hash = undef;
514     }
516     return ($msg, $msg_hash);
520 sub check_outgoing_xml_validity {
521     my ($msg) = @_;
523     my $msg_hash;
524     eval{
525         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
527         ##############
528         # check header
529         my $header_l = $msg_hash->{'header'};
530         if( 1 != @{$header_l} ) {
531             die 'no or more than one headers specified';
532         }
533         my $header = @{$header_l}[0];
534         if( 0 == length $header) {
535             die 'header has length 0';
536         }
538         ##############
539         # check source
540         my $source_l = $msg_hash->{'source'};
541         if( 1 != @{$source_l} ) {
542             die 'no or more than 1 sources specified';
543         }
544         my $source = @{$source_l}[0];
545         if( 0 == length $source) {
546             die 'source has length 0';
547         }
548         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
549                 $source =~ /^GOSA$/i ) {
550             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
551         }
552         
553         ##############
554         # check target  
555         my $target_l = $msg_hash->{'target'};
556         if( 0 == @{$target_l} ) {
557             die "no targets specified";
558         }
559         foreach my $target (@$target_l) {
560             if( 0 == length $target) {
561                 die "target has length 0";
562             }
563             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
564                     $target =~ /^GOSA$/i ||
565                     $target =~ /^\*$/ ||
566                     $target =~ /KNOWN_SERVER/i ||
567                     $target =~ /JOBDB/i ||
568                     $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 ){
569                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
570             }
571         }
572     };
573     if($@) {
574         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
575         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
576         $msg_hash = undef;
577     }
579     return ($msg_hash);
583 sub input_from_known_server {
584     my ($input, $remote_ip, $session_id) = @_ ;  
585     my ($msg, $msg_hash, $module);
587     my $sql_statement= "SELECT * FROM known_server";
588     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
590     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
591         my $host_name = $hit->{hostname};
592         if( not $host_name =~ "^$remote_ip") {
593             next;
594         }
595         my $host_key = $hit->{hostkey};
596         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
597         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
599         # check if module can open msg envelope with module key
600         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
601         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
602             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
603             daemon_log("$@", 8);
604             next;
605         }
606         else {
607             $msg = $tmp_msg;
608             $msg_hash = $tmp_msg_hash;
609             $module = "SIPackages";
610             last;
611         }
612     }
614     if( (!$msg) || (!$msg_hash) || (!$module) ) {
615         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
616     }
617   
618     return ($msg, $msg_hash, $module);
622 sub input_from_known_client {
623     my ($input, $remote_ip, $session_id) = @_ ;  
624     my ($msg, $msg_hash, $module);
626     my $sql_statement= "SELECT * FROM known_clients";
627     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
628     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
629         my $host_name = $hit->{hostname};
630         if( not $host_name =~ /^$remote_ip:\d*$/) {
631                 next;
632                 }
633         my $host_key = $hit->{hostkey};
634         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
635         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
637         # check if module can open msg envelope with module key
638         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
640         if( (!$msg) || (!$msg_hash) ) {
641             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
642             &daemon_log("$@", 8);
643             next;
644         }
645         else {
646             $module = "SIPackages";
647             last;
648         }
649     }
651     if( (!$msg) || (!$msg_hash) || (!$module) ) {
652         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
653     }
655     return ($msg, $msg_hash, $module);
659 sub input_from_unknown_host {
660     no strict "refs";
661     my ($input, $session_id) = @_ ;
662     my ($msg, $msg_hash, $module);
663     my $error_string;
664     
665         my %act_modules = %$known_modules;
667         while( my ($mod, $info) = each(%act_modules)) {
669         # check a key exists for this module
670         my $module_key = ${$mod."_key"};
671         if( not defined $module_key ) {
672             if( $mod eq 'ArpHandler' ) {
673                 next;
674             }
675             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
676             next;
677         }
678         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
680         # check if module can open msg envelope with module key
681         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
682         if( (not defined $msg) || (not defined $msg_hash) ) {
683             next;
684         }
685         else {
686             $module = $mod;
687             last;
688         }
689     }
691     if( (!$msg) || (!$msg_hash) || (!$module)) {
692         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
693     }
695     return ($msg, $msg_hash, $module);
699 sub create_ciphering {
700     my ($passwd) = @_;
701         if((!defined($passwd)) || length($passwd)==0) {
702                 $passwd = "";
703         }
704     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
705     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
706     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
707     $my_cipher->set_iv($iv);
708     return $my_cipher;
712 sub encrypt_msg {
713     my ($msg, $key) = @_;
714     my $my_cipher = &create_ciphering($key);
715     my $len;
716     {
717             use bytes;
718             $len= 16-length($msg)%16;
719     }
720     $msg = "\0"x($len).$msg;
721     $msg = $my_cipher->encrypt($msg);
722     chomp($msg = &encode_base64($msg));
723     # there are no newlines allowed inside msg
724     $msg=~ s/\n//g;
725     return $msg;
729 sub decrypt_msg {
731     my ($msg, $key) = @_ ;
732     $msg = &decode_base64($msg);
733     my $my_cipher = &create_ciphering($key);
734     $msg = $my_cipher->decrypt($msg); 
735     $msg =~ s/\0*//g;
736     return $msg;
740 sub get_encrypt_key {
741     my ($target) = @_ ;
742     my $encrypt_key;
743     my $error = 0;
745     # target can be in known_server
746     if( not defined $encrypt_key ) {
747         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
748         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
749         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
750             my $host_name = $hit->{hostname};
751             if( $host_name ne $target ) {
752                 next;
753             }
754             $encrypt_key = $hit->{hostkey};
755             last;
756         }
757     }
759     # target can be in known_client
760     if( not defined $encrypt_key ) {
761         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
762         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
763         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
764             my $host_name = $hit->{hostname};
765             if( $host_name ne $target ) {
766                 next;
767             }
768             $encrypt_key = $hit->{hostkey};
769             last;
770         }
771     }
773     return $encrypt_key;
777 #===  FUNCTION  ================================================================
778 #         NAME:  open_socket
779 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
780 #                [PeerPort] string necessary if port not appended by PeerAddr
781 #      RETURNS:  socket IO::Socket::INET
782 #  DESCRIPTION:  open a socket to PeerAddr
783 #===============================================================================
784 sub open_socket {
785     my ($PeerAddr, $PeerPort) = @_ ;
786     if(defined($PeerPort)){
787         $PeerAddr = $PeerAddr.":".$PeerPort;
788     }
789     my $socket;
790     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
791             Porto => "tcp",
792             Type => SOCK_STREAM,
793             Timeout => 5,
794             );
795     if(not defined $socket) {
796         return;
797     }
798 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
799     return $socket;
803 #===  FUNCTION  ================================================================
804 #         NAME:  get_ip 
805 #   PARAMETERS:  interface name (i.e. eth0)
806 #      RETURNS:  (ip address) 
807 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
808 #===============================================================================
809 sub get_ip {
810         my $ifreq= shift;
811         my $result= "";
812         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
813         my $proto= getprotobyname('ip');
815         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
816                 or die "socket: $!";
818         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
819                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
820                 my ($port, $addr) = sockaddr_in $sin;
821                 my $ip            = inet_ntoa $addr;
823                 if ($ip && length($ip) > 0) {
824                         $result = $ip;
825                 }
826         }
828         return $result;
832 sub get_local_ip_for_remote_ip {
833         my $remote_ip= shift;
834         my $result="0.0.0.0";
836         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
837                 if($remote_ip eq "127.0.0.1") {
838                         $result = "127.0.0.1";
839                 } else {
840                         my $PROC_NET_ROUTE= ('/proc/net/route');
842                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
843                                 or die "Could not open $PROC_NET_ROUTE";
845                         my @ifs = <PROC_NET_ROUTE>;
847                         close(PROC_NET_ROUTE);
849                         # Eat header line
850                         shift @ifs;
851                         chomp @ifs;
852                         foreach my $line(@ifs) {
853                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
854                                 my $destination;
855                                 my $mask;
856                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
857                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
858                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
859                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
860                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
861                                         # destination matches route, save mac and exit
862                                         $result= &get_ip($Iface);
863                                         last;
864                                 }
865                         }
866                 }
867         } else {
868                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
869         }
870         return $result;
874 sub send_msg_to_target {
875     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
876     my $error = 0;
877     my $header;
878     my $new_status;
879     my $act_status;
880     my ($sql_statement, $res);
881   
882     if( $msg_header ) {
883         $header = "'$msg_header'-";
884     } else {
885         $header = "";
886     }
888         # Patch the source ip
889         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
890                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
891                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
892         }
894     # encrypt xml msg
895     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
897     # opensocket
898     my $socket = &open_socket($address);
899     if( !$socket ) {
900         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
901         $error++;
902     }
903     
904     if( $error == 0 ) {
905         # send xml msg
906         print $socket $crypted_msg."\n";
908         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
909         #daemon_log("DEBUG: message:\n$msg", 9);
910         
911     }
913     # close socket in any case
914     if( $socket ) {
915         close $socket;
916     }
918     if( $error > 0 ) { $new_status = "down"; }
919     else { $new_status = $msg_header; }
922     # known_clients
923     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
924     $res = $known_clients_db->select_dbentry($sql_statement);
925     if( keys(%$res) > 0) {
926         $act_status = $res->{1}->{'status'};
927         if( $act_status eq "down" ) {
928             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
929             $res = $known_clients_db->del_dbentry($sql_statement);
930             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
931         } else { 
932             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
933             $res = $known_clients_db->update_dbentry($sql_statement);
934             if($new_status eq "down"){
935                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
936             } else {
937                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
938             }
939         }
940     }
942     # known_server
943     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
944     $res = $known_server_db->select_dbentry($sql_statement);
945     if( keys(%$res) > 0 ) {
946         $act_status = $res->{1}->{'status'};
947         if( $act_status eq "down" ) {
948             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
949             $res = $known_server_db->del_dbentry($sql_statement);
950             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
951         } 
952         else { 
953             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
954             $res = $known_server_db->update_dbentry($sql_statement);
955             if($new_status eq "down"){
956                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957             }
958             else {
959                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
960             }
961         }
962     }
963     return $error; 
967 sub update_jobdb_status_for_send_msgs {
968     my ($answer, $error) = @_;
969     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
970         my $jobdb_id = $1;
971             
972         # sending msg faild
973         if( $error ) {
974             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
975                 my $sql_statement = "UPDATE $job_queue_tn ".
976                     "SET status='error', result='can not deliver msg, please consult log file' ".
977                     "WHERE id=$jobdb_id";
978                 my $res = $job_db->update_dbentry($sql_statement);
979             }
981         # sending msg was successful
982         } else {
983             my $sql_statement = "UPDATE $job_queue_tn ".
984                 "SET status='done' ".
985                 "WHERE id=$jobdb_id AND status='processed'";
986             my $res = $job_db->update_dbentry($sql_statement);
987         }
988     }
991 sub _start {
992     my ($kernel) = $_[KERNEL];
993     &trigger_db_loop($kernel);
994     $global_kernel = $kernel;
995         $kernel->yield('create_fai_server_db', $fai_server_tn );
996         $kernel->yield('create_fai_release_db', $fai_release_tn );
997         $kernel->sig(USR1 => "sig_handler");
998         $kernel->sig(USR2 => "create_packages_list_db");
1001 sub sig_handler {
1002         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1003         daemon_log("0 INFO got signal '$signal'", 1); 
1004         $kernel->sig_handled();
1005         return;
1009 sub msg_to_decrypt {
1010     my ($session, $heap) = @_[SESSION, HEAP];
1011     my $session_id = $session->ID;
1012     my ($msg, $msg_hash, $module);
1013     my $error = 0;
1015     # hole neue msg aus @msgs_to_decrypt
1016     my $next_msg = shift @msgs_to_decrypt;
1017     
1018     # entschlüssle sie
1020     # msg is from a new client or gosa
1021     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1022     # msg is from a gosa-si-server or gosa-si-bus
1023     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1024         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1025     }
1026     # msg is from a gosa-si-client
1027     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1028         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1029     }
1030     # an error occurred
1031     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1032         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1033         # could not understand a msg from its server the client cause a re-registering process
1034         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);
1035         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1036         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1037         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1038             my $host_name = $hit->{'hostname'};
1039             my $host_key = $hit->{'hostkey'};
1040             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1041             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1042             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1043         }
1044         $error++;
1045     }
1046     
1047     # add message to incoming_db
1048     if( $error == 0) {
1049         my $header = @{$msg_hash->{'header'}}[0];
1050         my $target = @{$msg_hash->{'target'}}[0];
1051         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1052                 primkey=>[],
1053                 headertag=>$header,
1054                                 targettag=>$target,
1055                 xmlmessage=>$msg,
1056                 timestamp=>&get_time,
1057                 module=>$module,
1058                 } );
1059         if ($res != 0) {
1060                         # TODO ist das mit $! so ok???
1061             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1062         }
1063     }
1068 sub next_task {
1069     my ($session, $heap) = @_[SESSION, HEAP];
1070 #    if (keys( %{ $heap->{task} } ) < $max_children ) {
1071         my $task = POE::Wheel::Run->new(
1072                 Program => sub { process_task($session, $heap) },
1073                 StdioFilter => POE::Filter::Reference->new(),
1074                 StdoutEvent  => "task_result",
1075                 StderrEvent  => "task_debug",
1076                 CloseEvent   => "task_done",
1077                );
1079         $heap->{task}->{ $task->ID } = $task;
1080 #    }
1083 sub handle_task_result {
1084     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1085     my $client_answer = $result->{'answer'};
1086     if( $client_answer =~ s/session_id=(\d+)$// ) {
1087         my $session_id = $1;
1088         if( defined $session_id ) {
1089             my $session_reference = $kernel->ID_id_to_session($session_id);
1090             if( defined $session_reference ) {
1091                 $heap = $session_reference->get_heap();
1092             }
1093         }
1095         if(exists $heap->{'client'}) {
1096             $heap->{'client'}->put($client_answer);
1097         }
1098     }
1099     $kernel->sig(CHLD => "child_reap");
1102 sub handle_task_debug {
1103     my $result = $_[ARG0];
1104     print STDERR "$result\n";
1107 sub handle_task_done {
1108     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1109     delete $heap->{task}->{$task_id};
1112 sub process_task {
1113     no strict "refs";
1114     my ($session, $heap, $input) = @_;
1115     my $session_id = $session->ID;
1116     my $error = 0;
1117     my $answer_l;
1118     my ($answer_header, @answer_target_l, $answer_source);
1119     my $client_answer = "";
1121     daemon_log("", 5); 
1122     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1123     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1125         ##################################################
1126         # fetch first unprocessed message from incoming_db
1127     # sometimes the program is faster than sqlite, so wait until informations are present at db
1128     my $id_sql;
1129     my $id_res;
1130     my $message_id;
1131 # TODO : das hier ist sehr sehr unschön        
1132     while (1) {
1133         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1134         $id_res = $incoming_db->exec_statement($id_sql);
1135         $message_id = @{@$id_res[0]}[0];
1136         if (defined $message_id) { last }
1137     }
1139     # fetch new message from incoming_db
1140     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1141     my $res = $incoming_db->exec_statement($sql);
1143     # prepare all variables needed to process message
1144     my $msg = @{@$res[0]}[3];
1145     my $incoming_id = @{@$res[0]}[0];
1146     my $module = @{@$res[0]}[4];
1147     my $header =  @{@$res[0]}[2];
1148     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1150     # messages which are an answer to a still running process should not be processed here
1151     if ($header =~ /^answer_(\d+)/) {
1152         return;
1153     }
1154    
1155     # delete message from db 
1156     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1157     my $delete_res = $incoming_db->exec_statement($delete_sql);
1159     ######################
1160     # process incoming msg
1161     if( $error == 0) {
1162         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1163                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1164         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1165         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1167         if ( 0 < @{$answer_l} ) {
1168             my $answer_str = join("\n", @{$answer_l});
1169             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1170         } else {
1171             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1172         }
1174     }
1175     if( !$answer_l ) { $error++ };
1177     ########
1178     # answer
1179     if( $error == 0 ) {
1181         foreach my $answer ( @{$answer_l} ) {
1182             # check outgoing msg to xml validity
1183             my $answer_hash = &check_outgoing_xml_validity($answer);
1184             if( not defined $answer_hash ) {
1185                 next;
1186             }
1187             
1188             $answer_header = @{$answer_hash->{'header'}}[0];
1189             @answer_target_l = @{$answer_hash->{'target'}};
1190             $answer_source = @{$answer_hash->{'source'}}[0];
1192             # deliver msg to all targets 
1193             foreach my $answer_target ( @answer_target_l ) {
1195                 # targets of msg are all gosa-si-clients in known_clients_db
1196                 if( $answer_target eq "*" ) {
1197                     # answer is for all clients
1198                     my $sql_statement= "SELECT * FROM known_clients";
1199                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1200                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1201                         my $host_name = $hit->{hostname};
1202                         my $host_key = $hit->{hostkey};
1203                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1204                         &update_jobdb_status_for_send_msgs($answer, $error);
1205                     }
1206                 }
1208                 # targets of msg are all gosa-si-server in known_server_db
1209                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1210                     # answer is for all server in known_server
1211                     my $sql_statement= "SELECT * FROM known_server";
1212                     my $query_res = $known_server_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                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1217                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1218                         &update_jobdb_status_for_send_msgs($answer, $error);
1219                     }
1220                 }
1222                 # target of msg is GOsa
1223                                 elsif( $answer_target eq "GOSA" ) {
1224                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1225                                         my $add_on = "";
1226                     if( defined $session_id ) {
1227                         $add_on = ".session_id=$session_id";
1228                     }
1229                     # answer is for GOSA and has to returned to connected client
1230                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1231                     $client_answer = $gosa_answer.$add_on;
1232                 }
1234                 # target of msg is job queue at this host
1235                 elsif( $answer_target eq "JOBDB") {
1236                     $answer =~ /<header>(\S+)<\/header>/;   
1237                     my $header;
1238                     if( defined $1 ) { $header = $1; }
1239                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1240                     &update_jobdb_status_for_send_msgs($answer, $error);
1241                 }
1243                 # target of msg is a mac address
1244                 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 ) {
1245                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1246                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1247                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1248                     my $found_ip_flag = 0;
1249                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1250                         my $host_name = $hit->{hostname};
1251                         my $host_key = $hit->{hostkey};
1252                         $answer =~ s/$answer_target/$host_name/g;
1253                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1254                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1255                         &update_jobdb_status_for_send_msgs($answer, $error);
1256                         $found_ip_flag++ ;
1257                     }   
1258                     if( $found_ip_flag == 0) {
1259                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1260                         if( $bus_activ eq "true" ) { 
1261                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1262                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1263                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1264                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1265                                 my $bus_address = $hit->{hostname};
1266                                 my $bus_key = $hit->{hostkey};
1267                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1268                                 &update_jobdb_status_for_send_msgs($answer, $error);
1269                                 last;
1270                             }
1271                         }
1273                     }
1275                 #  answer is for one specific host   
1276                 } else {
1277                     # get encrypt_key
1278                     my $encrypt_key = &get_encrypt_key($answer_target);
1279                     if( not defined $encrypt_key ) {
1280                         # unknown target, forward msg to bus
1281                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1282                         if( $bus_activ eq "true" ) { 
1283                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1284                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1285                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1286                             my $res_length = keys( %{$query_res} );
1287                             if( $res_length == 0 ){
1288                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1289                                         "no bus found in known_server", 3);
1290                             }
1291                             else {
1292                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1293                                     my $bus_key = $hit->{hostkey};
1294                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1295                                     &update_jobdb_status_for_send_msgs($answer, $error);
1296                                 }
1297                             }
1298                         }
1299                         next;
1300                     }
1301                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1302                     &update_jobdb_status_for_send_msgs($answer, $error);
1303                 }
1304             }
1305         }
1306     }
1308     my $filter = POE::Filter::Reference->new();
1309     my %result = ( 
1310             status => "seems ok to me",
1311             answer => $client_answer,
1312             );
1314     my $output = $filter->put( [ \%result ] );
1315     print @$output;
1321 sub trigger_db_loop {
1322         my ($kernel) = @_ ;
1323         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1324         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1325         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1326     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1327         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1331 sub watch_for_done_jobs {
1332     my ($kernel,$heap) = @_[KERNEL, HEAP];
1334     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1335         " WHERE status='done'";
1336         my $res = $job_db->select_dbentry( $sql_statement );
1338     while( my ($id, $hit) = each %{$res} ) {
1339         my $jobdb_id = $hit->{id};
1340         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1341         my $res = $job_db->del_dbentry($sql_statement); 
1342     }
1344     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1348 sub watch_for_new_jobs {
1349         if($watch_for_new_jobs_in_progress == 0) {
1350                 $watch_for_new_jobs_in_progress = 1;
1351                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1353                 # check gosa job queue for jobs with executable timestamp
1354                 my $timestamp = &get_time();
1355                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1356                 my $res = $job_db->exec_statement( $sql_statement );
1358                 # Merge all new jobs that would do the same actions
1359                 my @drops;
1360                 my $hits;
1361                 foreach my $hit (reverse @{$res} ) {
1362                         my $macaddress= lc @{$hit}[8];
1363                         my $headertag= @{$hit}[5];
1364                         if(
1365                                 defined($hits->{$macaddress}) &&
1366                                 defined($hits->{$macaddress}->{$headertag}) &&
1367                                 defined($hits->{$macaddress}->{$headertag}[0])
1368                         ) {
1369                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1370                         }
1371                         $hits->{$macaddress}->{$headertag}= $hit;
1372                 }
1374                 # Delete new jobs with a matching job in state 'processing'
1375                 foreach my $macaddress (keys %{$hits}) {
1376                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1377                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1378                                 if(defined($jobdb_id)) {
1379                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1380                                         my $res = $job_db->exec_statement( $sql_statement );
1381                                         foreach my $hit (@{$res}) {
1382                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1383                                         }
1384                                 } else {
1385                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1386                                 }
1387                         }
1388                 }
1390                 # Commit deletion
1391                 $job_db->exec_statementlist(\@drops);
1393                 # Look for new jobs that could be executed
1394                 foreach my $macaddress (keys %{$hits}) {
1396                         # Look if there is an executing job
1397                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1398                         my $res = $job_db->exec_statement( $sql_statement );
1400                         # Skip new jobs for host if there is a processing job
1401                         if(defined($res) and defined @{$res}[0]) {
1402                                 next;
1403                         }
1405                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1406                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1407                                 if(defined($jobdb_id)) {
1408                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1410                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1411                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1412                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1414                                         # expect macaddress is unique!!!!!!
1415                                         my $target = $res_hash->{1}->{hostname};
1417                                         # change header
1418                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1420                                         # add sqlite_id
1421                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1423                                         $job_msg =~ /<header>(\S+)<\/header>/;
1424                                         my $header = $1 ;
1425                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1427                                         # update status in job queue to 'processing'
1428                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1429                                         my $res = $job_db->update_dbentry($sql_statement);
1430 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1432                                         # We don't want parallel processing
1433                                         last;
1434                                 }
1435                         }
1436                 }
1438                 $watch_for_new_jobs_in_progress = 0;
1439                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1440         }
1444 sub watch_for_new_messages {
1445     my ($kernel,$heap) = @_[KERNEL, HEAP];
1446     my @coll_user_msg;   # collection list of outgoing messages
1447     
1448     # check messaging_db for new incoming messages with executable timestamp
1449     my $timestamp = &get_time();
1450     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1451     my $res = $messaging_db->exec_statement( $sql_statement );
1452         foreach my $hit (@{$res}) {
1454         # create outgoing messages
1455         my $message_to = @{$hit}[3];
1456         # translate message_to to plain login name
1457         my @message_to_l = split(/,/, $message_to);  
1458                 my %receiver_h; 
1459                 foreach my $receiver (@message_to_l) {
1460                         if ($receiver =~ /^u_([\s\S]*)$/) {
1461                                 $receiver_h{$1} = 0;
1462                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1463                                 my $group_name = $1;
1464                                 # fetch all group members from ldap and add them to receiver hash
1465                                 my $ldap_handle = &get_ldap_handle($session_id);
1466                                 if (defined $ldap_handle) {
1467                                                 my $mesg = $ldap_handle->search(
1468                                                                                 base => $ldap_base,
1469                                                                                 scope => 'sub',
1470                                                                                 attrs => ['memberUid'],
1471                                                                                 filter => "cn=$group_name"; 
1472                                                                                 );
1473                                                 if ($mesg->count) {
1474                                                                 my @entries = $mesg->entries;
1475                                                                 foreach my $entry (@entries) {
1476                                                                                 my @receivers= $entry->get_value("memberUid");
1477                                                                                 foreach my $receiver (@receivers) { 
1478                                                                                                 $receiver_h{$1} = 0;
1479                                                                                 }
1480                                                                 }
1481                                                 } 
1482                                                 # translating errors ?
1483                                                 if ($mesg->code) {
1484                                                                 daemon_log("$session_id ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1485                                                 }
1486                                 # ldap handle error ?           
1487                                 } else {
1488                                         daemon_log("$session_id ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1489                                 }
1490                         } else {
1491                                 my $sbjct = &encode_base64(@{$hit}[1]);
1492                                 my $msg = &encode_base64(@{$hit}[7]);
1493                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1494                         }
1495                 }
1496                 my @receiver_l = keys(%receiver_h);
1498         my $message_id = @{$hit}[0];
1500         #add each outgoing msg to messaging_db
1501         my $receiver;
1502         foreach $receiver (@receiver_l) {
1503             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1504                 "VALUES ('".
1505                 $message_id."', '".    # id
1506                 @{$hit}[1]."', '".     # subject
1507                 @{$hit}[2]."', '".     # message_from
1508                 $receiver."', '".      # message_to
1509                 "none"."', '".         # flag
1510                 "out"."', '".          # direction
1511                 @{$hit}[6]."', '".     # delivery_time
1512                 @{$hit}[7]."', '".     # message
1513                 $timestamp."'".     # timestamp
1514                 ")";
1515             &daemon_log("M DEBUG: $sql_statement", 1);
1516             my $res = $messaging_db->exec_statement($sql_statement);
1517             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1518         }
1520         # set incoming message to flag d=deliverd
1521         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1522         &daemon_log("M DEBUG: $sql_statement", 7);
1523         $res = $messaging_db->update_dbentry($sql_statement);
1524         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1525     }
1527     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1528     return;
1531 sub watch_for_delivery_messages {
1532     my ($kernel, $heap) = @_[KERNEL, HEAP];
1534     # select outgoing messages
1535     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1536     #&daemon_log("0 DEBUG: $sql", 7);
1537     my $res = $messaging_db->exec_statement( $sql_statement );
1538     
1539     # build out msg for each    usr
1540     foreach my $hit (@{$res}) {
1541         my $receiver = @{$hit}[3];
1542         my $msg_id = @{$hit}[0];
1543         my $subject = @{$hit}[1];
1544         my $message = @{$hit}[7];
1546         # resolve usr -> host where usr is logged in
1547         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1548         #&daemon_log("0 DEBUG: $sql", 7);
1549         my $res = $login_users_db->exec_statement($sql);
1551         # reciver is logged in nowhere
1552         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1554                 my $send_succeed = 0;
1555                 foreach my $hit (@$res) {
1556                                 my $receiver_host = @$hit[0];
1557                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1559                                 # fetch key to encrypt msg propperly for usr/host
1560                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1561                                 &daemon_log("0 DEBUG: $sql", 7);
1562                                 my $res = $known_clients_db->exec_statement($sql);
1564                                 # host is already down
1565                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1567                                 # host is on
1568                                 my $receiver_key = @{@{$res}[0]}[2];
1569                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1570                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1571                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1572                                 if ($error == 0 ) {
1573                                         $send_succeed++ ;
1574                                 }
1575                 }
1577                 if ($send_succeed) {
1578                                 # set outgoing msg at db to deliverd
1579                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1580                                 &daemon_log("0 DEBUG: $sql", 7);
1581                                 my $res = $messaging_db->exec_statement($sql); 
1582                 }
1583         }
1585     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1586     return;
1590 sub watch_for_done_messages {
1591     my ($kernel,$heap) = @_[KERNEL, HEAP];
1593     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1594     #&daemon_log("0 DEBUG: $sql", 7);
1595     my $res = $messaging_db->exec_statement($sql); 
1597     foreach my $hit (@{$res}) {
1598         my $msg_id = @{$hit}[0];
1600         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1601         #&daemon_log("0 DEBUG: $sql", 7); 
1602         my $res = $messaging_db->exec_statement($sql);
1604         # not all usr msgs have been seen till now
1605         if ( ref(@$res[0]) eq "ARRAY") { next; }
1606         
1607         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1608         #&daemon_log("0 DEBUG: $sql", 7);
1609         $res = $messaging_db->exec_statement($sql);
1610     
1611     }
1613     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1614     return;
1618 sub get_ldap_handle {
1619         my ($session_id) = @_;
1620         my $heap;
1621         my $ldap_handle;
1623         if (not defined $session_id ) { $session_id = 0 };
1624         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1626         if ($session_id == 0) {
1627                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1628                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1629                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1631         } else {
1632                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1633                 if( defined $session_reference ) {
1634                         $heap = $session_reference->get_heap();
1635                 }
1637                 if (not defined $heap) {
1638                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1639                         return;
1640                 }
1642                 # TODO: This "if" is nonsense, because it doesn't prove that the
1643                 #       used handle is still valid - or if we've to reconnect...
1644                 #if (not exists $heap->{ldap_handle}) {
1645                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1646                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1647                         $heap->{ldap_handle} = $ldap_handle;
1648                 #}
1649         }
1650         return $ldap_handle;
1654 sub change_fai_state {
1655     my ($st, $targets, $session_id) = @_;
1656     $session_id = 0 if not defined $session_id;
1657     # Set FAI state to localboot
1658     my %mapActions= (
1659         reboot    => '',
1660         update    => 'softupdate',
1661         localboot => 'localboot',
1662         reinstall => 'install',
1663         rescan    => '',
1664         wake      => '',
1665         memcheck  => 'memcheck',
1666         sysinfo   => 'sysinfo',
1667         install   => 'install',
1668     );
1670     # Return if this is unknown
1671     if (!exists $mapActions{ $st }){
1672         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1673       return;
1674     }
1676     my $state= $mapActions{ $st };
1678     my $ldap_handle = &get_ldap_handle($session_id);
1679     if( defined($ldap_handle) ) {
1681       # Build search filter for hosts
1682         my $search= "(&(objectClass=GOhard)";
1683         foreach (@{$targets}){
1684             $search.= "(macAddress=$_)";
1685         }
1686         $search.= ")";
1688       # If there's any host inside of the search string, procress them
1689         if (!($search =~ /macAddress/)){
1690             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1691             return;
1692         }
1694       # Perform search for Unit Tag
1695       my $mesg = $ldap_handle->search(
1696           base   => $ldap_base,
1697           scope  => 'sub',
1698           attrs  => ['dn', 'FAIstate', 'objectClass'],
1699           filter => "$search"
1700           );
1702           if ($mesg->count) {
1703                   my @entries = $mesg->entries;
1704                   foreach my $entry (@entries) {
1705                           # Only modify entry if it is not set to '$state'
1706                           if ($entry->get_value("FAIstate") ne "$state"){
1707                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1708                                   my $result;
1709                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1710                                   if (exists $tmp{'FAIobject'}){
1711                                           if ($state eq ''){
1712                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1713                                                           delete => [ FAIstate => [] ] ]);
1714                                           } else {
1715                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1716                                                           replace => [ FAIstate => $state ] ]);
1717                                           }
1718                                   } elsif ($state ne ''){
1719                                           $result= $ldap_handle->modify($entry->dn, changes => [
1720                                                   add     => [ objectClass => 'FAIobject' ],
1721                                                   add     => [ FAIstate => $state ] ]);
1722                                   }
1724                                   # Errors?
1725                                   if ($result->code){
1726                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1727                                   }
1728                           } else {
1729                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1730                           }  
1731                   }
1732           }
1733     # if no ldap handle defined
1734     } else {
1735         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1736     }
1741 sub change_goto_state {
1742     my ($st, $targets, $session_id) = @_;
1743     $session_id = 0  if not defined $session_id;
1745     # Switch on or off?
1746     my $state= $st eq 'active' ? 'active': 'locked';
1748     my $ldap_handle = &get_ldap_handle($session_id);
1749     if( defined($ldap_handle) ) {
1751       # Build search filter for hosts
1752       my $search= "(&(objectClass=GOhard)";
1753       foreach (@{$targets}){
1754         $search.= "(macAddress=$_)";
1755       }
1756       $search.= ")";
1758       # If there's any host inside of the search string, procress them
1759       if (!($search =~ /macAddress/)){
1760         return;
1761       }
1763       # Perform search for Unit Tag
1764       my $mesg = $ldap_handle->search(
1765           base   => $ldap_base,
1766           scope  => 'sub',
1767           attrs  => ['dn', 'gotoMode'],
1768           filter => "$search"
1769           );
1771       if ($mesg->count) {
1772         my @entries = $mesg->entries;
1773         foreach my $entry (@entries) {
1775           # Only modify entry if it is not set to '$state'
1776           if ($entry->get_value("gotoMode") ne $state){
1778             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1779             my $result;
1780             $result= $ldap_handle->modify($entry->dn, changes => [
1781                                                 replace => [ gotoMode => $state ] ]);
1783             # Errors?
1784             if ($result->code){
1785               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1786             }
1788           }
1789         }
1790       }
1792     }
1796 sub run_create_fai_server_db {
1797     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1798     my $session_id = $session->ID;
1799     my $task = POE::Wheel::Run->new(
1800             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1801             StdoutEvent  => "session_run_result",
1802             StderrEvent  => "session_run_debug",
1803             CloseEvent   => "session_run_done",
1804             );
1806     $heap->{task}->{ $task->ID } = $task;
1807     return;
1811 sub create_fai_server_db {
1812     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1813         my $result;
1815         if (not defined $session_id) { $session_id = 0; }
1816     my $ldap_handle = &get_ldap_handle();
1817         if(defined($ldap_handle)) {
1818                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1819                 my $mesg= $ldap_handle->search(
1820                         base   => $ldap_base,
1821                         scope  => 'sub',
1822                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1823                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1824                 );
1825                 if($mesg->{'resultCode'} == 0 &&
1826                    $mesg->count != 0) {
1827                    foreach my $entry (@{$mesg->{entries}}) {
1828                            if($entry->exists('FAIrepository')) {
1829                                    # Add an entry for each Repository configured for server
1830                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1831                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1832                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1833                                                    $result= $fai_server_db->add_dbentry( { 
1834                                                                    table => $table_name,
1835                                                                    primkey => ['server', 'release', 'tag'],
1836                                                                    server => $tmp_url,
1837                                                                    release => $tmp_release,
1838                                                                    sections => $tmp_sections,
1839                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1840                                                            } );
1841                                            }
1842                                    }
1843                            }
1844                    }
1845                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1847                 # TODO: Find a way to post the 'create_packages_list_db' event
1848                 if(not defined($dont_create_packages_list)) {
1849                         &create_packages_list_db(undef, undef, $session_id);
1850                 }
1851         }       
1852     
1853     $ldap_handle->disconnect;
1854         return $result;
1858 sub run_create_fai_release_db {
1859     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1860         my $session_id = $session->ID;
1861     my $task = POE::Wheel::Run->new(
1862             Program => sub { &create_fai_release_db($table_name, $session_id) },
1863             StdoutEvent  => "session_run_result",
1864             StderrEvent  => "session_run_debug",
1865             CloseEvent   => "session_run_done",
1866             );
1868     $heap->{task}->{ $task->ID } = $task;
1869     return;
1873 sub create_fai_release_db {
1874         my ($table_name, $session_id) = @_;
1875         my $result;
1877     # used for logging
1878     if (not defined $session_id) { $session_id = 0; }
1880     my $ldap_handle = &get_ldap_handle();
1881         if(defined($ldap_handle)) {
1882                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1883                 my $mesg= $ldap_handle->search(
1884                         base   => $ldap_base,
1885                         scope  => 'sub',
1886                         attrs  => [],
1887                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1888                 );
1889                 if($mesg->{'resultCode'} == 0 &&
1890                         $mesg->count != 0) {
1891                         # Walk through all possible FAI container ou's
1892                         my @sql_list;
1893                         my $timestamp= &get_time();
1894                         foreach my $ou (@{$mesg->{entries}}) {
1895                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1896                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1897                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1898                                         if(@tmp_array) {
1899                                                 foreach my $entry (@tmp_array) {
1900                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1901                                                                 my $sql= 
1902                                                                 "INSERT INTO $table_name "
1903                                                                 ."(timestamp, release, class, type, state) VALUES ("
1904                                                                 .$timestamp.","
1905                                                                 ."'".$entry->{'release'}."',"
1906                                                                 ."'".$entry->{'class'}."',"
1907                                                                 ."'".$entry->{'type'}."',"
1908                                                                 ."'".$entry->{'state'}."')";
1909                                                                 push @sql_list, $sql;
1910                                                         }
1911                                                 }
1912                                         }
1913                                 }
1914                         }
1916                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1917                         if(@sql_list) {
1918                                 unshift @sql_list, "VACUUM";
1919                                 unshift @sql_list, "DELETE FROM $table_name";
1920                                 $fai_release_db->exec_statementlist(\@sql_list);
1921                         }
1922                         daemon_log("$session_id DEBUG: Done with inserting",7);
1923                 }
1924                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1925         }
1926     $ldap_handle->disconnect;
1927         return $result;
1930 sub get_fai_types {
1931         my $tmp_classes = shift || return undef;
1932         my @result;
1934         foreach my $type(keys %{$tmp_classes}) {
1935                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1936                         my $entry = {
1937                                 type => $type,
1938                                 state => $tmp_classes->{$type}[0],
1939                         };
1940                         push @result, $entry;
1941                 }
1942         }
1944         return @result;
1947 sub get_fai_state {
1948         my $result = "";
1949         my $tmp_classes = shift || return $result;
1951         foreach my $type(keys %{$tmp_classes}) {
1952                 if(defined($tmp_classes->{$type}[0])) {
1953                         $result = $tmp_classes->{$type}[0];
1954                         
1955                 # State is equal for all types in class
1956                         last;
1957                 }
1958         }
1960         return $result;
1963 sub resolve_fai_classes {
1964         my ($fai_base, $ldap_handle, $session_id) = @_;
1965         if (not defined $session_id) { $session_id = 0; }
1966         my $result;
1967         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1968         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1969         my $fai_classes;
1971         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1972         my $mesg= $ldap_handle->search(
1973                 base   => $fai_base,
1974                 scope  => 'sub',
1975                 attrs  => ['cn','objectClass','FAIstate'],
1976                 filter => $fai_filter,
1977         );
1978         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1980         if($mesg->{'resultCode'} == 0 &&
1981                 $mesg->count != 0) {
1982                 foreach my $entry (@{$mesg->{entries}}) {
1983                         if($entry->exists('cn')) {
1984                                 my $tmp_dn= $entry->dn();
1986                                 # Skip classname and ou dn parts for class
1987                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1989                                 # Skip classes without releases
1990                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1991                                         next;
1992                                 }
1994                                 my $tmp_cn= $entry->get_value('cn');
1995                                 my $tmp_state= $entry->get_value('FAIstate');
1997                                 my $tmp_type;
1998                                 # Get FAI type
1999                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2000                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2001                                                 $tmp_type= $oclass;
2002                                                 last;
2003                                         }
2004                                 }
2006                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2007                                         # A Subrelease
2008                                         my @sub_releases = split(/,/, $tmp_release);
2010                                         # Walk through subreleases and build hash tree
2011                                         my $hash;
2012                                         while(my $tmp_sub_release = pop @sub_releases) {
2013                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2014                                         }
2015                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2016                                 } else {
2017                                         # A branch, no subrelease
2018                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2019                                 }
2020                         } elsif (!$entry->exists('cn')) {
2021                                 my $tmp_dn= $entry->dn();
2022                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2024                                 # Skip classes without releases
2025                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2026                                         next;
2027                                 }
2029                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2030                                         # A Subrelease
2031                                         my @sub_releases= split(/,/, $tmp_release);
2033                                         # Walk through subreleases and build hash tree
2034                                         my $hash;
2035                                         while(my $tmp_sub_release = pop @sub_releases) {
2036                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2037                                         }
2038                                         # Remove the last two characters
2039                                         chop($hash);
2040                                         chop($hash);
2042                                         eval('$fai_classes->'.$hash.'= {}');
2043                                 } else {
2044                                         # A branch, no subrelease
2045                                         if(!exists($fai_classes->{$tmp_release})) {
2046                                                 $fai_classes->{$tmp_release} = {};
2047                                         }
2048                                 }
2049                         }
2050                 }
2052                 # The hash is complete, now we can honor the copy-on-write based missing entries
2053                 foreach my $release (keys %$fai_classes) {
2054                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2055                 }
2056         }
2057         return $result;
2060 sub apply_fai_inheritance {
2061        my $fai_classes = shift || return {};
2062        my $tmp_classes;
2064        # Get the classes from the branch
2065        foreach my $class (keys %{$fai_classes}) {
2066                # Skip subreleases
2067                if($class =~ /^ou=.*$/) {
2068                        next;
2069                } else {
2070                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2071                }
2072        }
2074        # Apply to each subrelease
2075        foreach my $subrelease (keys %{$fai_classes}) {
2076                if($subrelease =~ /ou=/) {
2077                        foreach my $tmp_class (keys %{$tmp_classes}) {
2078                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2079                                        $fai_classes->{$subrelease}->{$tmp_class} =
2080                                        deep_copy($tmp_classes->{$tmp_class});
2081                                } else {
2082                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2083                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2084                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2085                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2086                                                }
2087                                        }
2088                                }
2089                        }
2090                }
2091        }
2093        # Find subreleases in deeper levels
2094        foreach my $subrelease (keys %{$fai_classes}) {
2095                if($subrelease =~ /ou=/) {
2096                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2097                                if($subsubrelease =~ /ou=/) {
2098                                        apply_fai_inheritance($fai_classes->{$subrelease});
2099                                }
2100                        }
2101                }
2102        }
2104        return $fai_classes;
2107 sub get_fai_release_entries {
2108         my $tmp_classes = shift || return;
2109         my $parent = shift || "";
2110         my @result = shift || ();
2112         foreach my $entry (keys %{$tmp_classes}) {
2113                 if(defined($entry)) {
2114                         if($entry =~ /^ou=.*$/) {
2115                                 my $release_name = $entry;
2116                                 $release_name =~ s/ou=//g;
2117                                 if(length($parent)>0) {
2118                                         $release_name = $parent."/".$release_name;
2119                                 }
2120                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2121                                 foreach my $bufentry(@bufentries) {
2122                                         push @result, $bufentry;
2123                                 }
2124                         } else {
2125                                 my @types = get_fai_types($tmp_classes->{$entry});
2126                                 foreach my $type (@types) {
2127                                         push @result, 
2128                                         {
2129                                                 'class' => $entry,
2130                                                 'type' => $type->{'type'},
2131                                                 'release' => $parent,
2132                                                 'state' => $type->{'state'},
2133                                         };
2134                                 }
2135                         }
2136                 }
2137         }
2139         return @result;
2142 sub deep_copy {
2143         my $this = shift;
2144         if (not ref $this) {
2145                 $this;
2146         } elsif (ref $this eq "ARRAY") {
2147                 [map deep_copy($_), @$this];
2148         } elsif (ref $this eq "HASH") {
2149                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2150         } else { die "what type is $_?" }
2154 sub session_run_result {
2155     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2156     $kernel->sig(CHLD => "child_reap");
2159 sub session_run_debug {
2160     my $result = $_[ARG0];
2161     print STDERR "$result\n";
2164 sub session_run_done {
2165     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2166     delete $heap->{task}->{$task_id};
2170 sub create_sources_list {
2171         my $session_id = shift;
2172         my $ldap_handle = &main::get_ldap_handle;
2173         my $result="/tmp/gosa_si_tmp_sources_list";
2175         # Remove old file
2176         if(stat($result)) {
2177                 unlink($result);
2178                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2179         }
2181         my $fh;
2182         open($fh, ">$result");
2183         if (not defined $fh) {
2184                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2185                 return undef;
2186         }
2187         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2188                 my $mesg=$ldap_handle->search(
2189                         base    => $main::ldap_server_dn,
2190                         scope   => 'base',
2191                         attrs   => 'FAIrepository',
2192                         filter  => 'objectClass=FAIrepositoryServer'
2193                 );
2194                 if($mesg->count) {
2195                         foreach my $entry(@{$mesg->{'entries'}}) {
2196                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2197                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2198                                         my $line = "deb $server $release";
2199                                         $sections =~ s/,/ /g;
2200                                         $line.= " $sections";
2201                                         print $fh $line."\n";
2202                                 }
2203                         }
2204                 }
2205         } else {
2206                 if (defined $main::ldap_server_dn){
2207                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2208                 } else {
2209                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2210                 }
2211         }
2212         close($fh);
2214         return $result;
2218 sub run_create_packages_list_db {
2219     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2220         my $session_id = $session->ID;
2222         my $task = POE::Wheel::Run->new(
2223                                         Priority => +20,
2224                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2225                                         StdoutEvent  => "session_run_result",
2226                                         StderrEvent  => "session_run_debug",
2227                                         CloseEvent   => "session_run_done",
2228                                         );
2229         $heap->{task}->{ $task->ID } = $task;
2233 sub create_packages_list_db {
2234         my ($ldap_handle, $sources_file, $session_id) = @_;
2235         
2236         # it should not be possible to trigger a recreation of packages_list_db
2237         # while packages_list_db is under construction, so set flag packages_list_under_construction
2238         # which is tested befor recreation can be started
2239         if (-r $packages_list_under_construction) {
2240                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2241                 return;
2242         } else {
2243                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2244                 # set packages_list_under_construction to true
2245                 system("touch $packages_list_under_construction");
2246                 @packages_list_statements=();
2247         }
2249         if (not defined $session_id) { $session_id = 0; }
2250         if (not defined $ldap_handle) { 
2251                 $ldap_handle= &get_ldap_handle();
2253                 if (not defined $ldap_handle) {
2254                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2255                         unlink($packages_list_under_construction);
2256                         return;
2257                 }
2258         }
2259         if (not defined $sources_file) { 
2260                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2261                 $sources_file = &create_sources_list($session_id);
2262         }
2264         if (not defined $sources_file) {
2265                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2266                 unlink($packages_list_under_construction);
2267                 return;
2268         }
2270         my $line;
2272         open(CONFIG, "<$sources_file") or do {
2273                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2274                 unlink($packages_list_under_construction);
2275                 return;
2276         };
2278         # Read lines
2279         while ($line = <CONFIG>){
2280                 # Unify
2281                 chop($line);
2282                 $line =~ s/^\s+//;
2283                 $line =~ s/^\s+/ /;
2285                 # Strip comments
2286                 $line =~ s/#.*$//g;
2288                 # Skip empty lines
2289                 if ($line =~ /^\s*$/){
2290                         next;
2291                 }
2293                 # Interpret deb line
2294                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2295                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2296                         my $section;
2297                         foreach $section (split(' ', $sections)){
2298                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2299                         }
2300                 }
2301         }
2303         close (CONFIG);
2305         find(\&cleanup_and_extract, keys( %repo_dirs ));
2306         &main::strip_packages_list_statements();
2307         unshift @packages_list_statements, "VACUUM";
2308         $packages_list_db->exec_statementlist(\@packages_list_statements);
2309         unlink($packages_list_under_construction);
2310         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2311         return;
2314 # This function should do some intensive task to minimize the db-traffic
2315 sub strip_packages_list_statements {
2316     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2317         my @new_statement_list=();
2318         my $hash;
2319         my $insert_hash;
2320         my $update_hash;
2321         my $delete_hash;
2322         my $local_timestamp=get_time();
2324         foreach my $existing_entry (@existing_entries) {
2325                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2326         }
2328         foreach my $statement (@packages_list_statements) {
2329                 if($statement =~ /^INSERT/i) {
2330                         # Assign the values from the insert statement
2331                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2332                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2333                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2334                                 # If section or description has changed, update the DB
2335                                 if( 
2336                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2337                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2338                                 ) {
2339                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2340                                 }
2341                         } else {
2342                                 # Insert a non-existing entry to db
2343                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2344                         }
2345                 } elsif ($statement =~ /^UPDATE/i) {
2346                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2347                         /^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;
2348                         foreach my $distribution (keys %{$hash}) {
2349                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2350                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2351                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2352                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2353                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2354                                                 my $section;
2355                                                 my $description;
2356                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2357                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2358                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2359                                                 }
2360                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2361                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2362                                                 }
2363                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2364                                         }
2365                                 }
2366                         }
2367                 }
2368         }
2370         # TODO: Check for orphaned entries
2372         # unroll the insert_hash
2373         foreach my $distribution (keys %{$insert_hash}) {
2374                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2375                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2376                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2377                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2378                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2379                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2380                                 ."'$local_timestamp')";
2381                         }
2382                 }
2383         }
2385         # unroll the update hash
2386         foreach my $distribution (keys %{$update_hash}) {
2387                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2388                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2389                                 my $set = "";
2390                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2391                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2392                                 }
2393                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2394                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2395                                 }
2396                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2397                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2398                                 }
2399                                 if(defined($set) and length($set) > 0) {
2400                                         $set .= "timestamp = '$local_timestamp'";
2401                                 } else {
2402                                         next;
2403                                 }
2404                                 push @new_statement_list, 
2405                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2406                                         ." distribution = '$distribution'"
2407                                         ." AND package = '$package'"
2408                                         ." AND version = '$version'";
2409                         }
2410                 }
2411         }
2413         @packages_list_statements = @new_statement_list;
2417 sub parse_package_info {
2418     my ($baseurl, $dist, $section, $session_id)= @_;
2419     my ($package);
2420     if (not defined $session_id) { $session_id = 0; }
2421     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2422     $repo_dirs{ "${repo_path}/pool" } = 1;
2424     foreach $package ("Packages.gz"){
2425         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2426         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2427         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2428     }
2429     
2433 sub get_package {
2434     my ($url, $dest, $session_id)= @_;
2435     if (not defined $session_id) { $session_id = 0; }
2437     my $tpath = dirname($dest);
2438     -d "$tpath" || mkpath "$tpath";
2440     # This is ugly, but I've no time to take a look at "how it works in perl"
2441     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2442         system("gunzip -cd '$dest' > '$dest.in'");
2443         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2444         unlink($dest);
2445         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2446     } else {
2447         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2448     }
2449     return 0;
2453 sub parse_package {
2454     my ($path, $dist, $srv_path, $session_id)= @_;
2455     if (not defined $session_id) { $session_id = 0;}
2456     my ($package, $version, $section, $description);
2457     my $PACKAGES;
2458     my $timestamp = &get_time();
2460     if(not stat("$path.in")) {
2461         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2462         return;
2463     }
2465     open($PACKAGES, "<$path.in");
2466     if(not defined($PACKAGES)) {
2467         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2468         return;
2469     }
2471     # Read lines
2472     while (<$PACKAGES>){
2473         my $line = $_;
2474         # Unify
2475         chop($line);
2477         # Use empty lines as a trigger
2478         if ($line =~ /^\s*$/){
2479             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2480             push(@packages_list_statements, $sql);
2481             $package = "none";
2482             $version = "none";
2483             $section = "none";
2484             $description = "none"; 
2485             next;
2486         }
2488         # Trigger for package name
2489         if ($line =~ /^Package:\s/){
2490             ($package)= ($line =~ /^Package: (.*)$/);
2491             next;
2492         }
2494         # Trigger for version
2495         if ($line =~ /^Version:\s/){
2496             ($version)= ($line =~ /^Version: (.*)$/);
2497             next;
2498         }
2500         # Trigger for description
2501         if ($line =~ /^Description:\s/){
2502             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2503             next;
2504         }
2506         # Trigger for section
2507         if ($line =~ /^Section:\s/){
2508             ($section)= ($line =~ /^Section: (.*)$/);
2509             next;
2510         }
2512         # Trigger for filename
2513         if ($line =~ /^Filename:\s/){
2514             my ($filename) = ($line =~ /^Filename: (.*)$/);
2515             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2516             next;
2517         }
2518     }
2520     close( $PACKAGES );
2521     unlink( "$path.in" );
2522     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2526 sub store_fileinfo {
2527     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2529     my %fileinfo = (
2530         'package' => $package,
2531         'dist' => $dist,
2532         'version' => $vers,
2533     );
2535     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2539 sub cleanup_and_extract {
2540     my $fileinfo = $repo_files{ $File::Find::name };
2542     if( defined $fileinfo ) {
2544         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2545         my $sql;
2546         my $package = $fileinfo->{ 'package' };
2547         my $newver = $fileinfo->{ 'version' };
2549         mkpath($dir);
2550         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2552                 if( -f "$dir/DEBIAN/templates" ) {
2554                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2556                         my $tmpl= "";
2557                         {
2558                                 local $/=undef;
2559                                 open FILE, "$dir/DEBIAN/templates";
2560                                 $tmpl = &encode_base64(<FILE>);
2561                                 close FILE;
2562                         }
2563                         rmtree("$dir/DEBIAN/templates");
2565                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2566                 push @packages_list_statements, $sql;
2567                 }
2568     }
2570     return;
2574 #==== MAIN = main ==============================================================
2575 #  parse commandline options
2576 Getopt::Long::Configure( "bundling" );
2577 GetOptions("h|help" => \&usage,
2578         "c|config=s" => \$cfg_file,
2579         "f|foreground" => \$foreground,
2580         "v|verbose+" => \$verbose,
2581         "no-bus+" => \$no_bus,
2582         "no-arp+" => \$no_arp,
2583            );
2585 #  read and set config parameters
2586 &check_cmdline_param ;
2587 &read_configfile;
2588 &check_pid;
2590 $SIG{CHLD} = 'IGNORE';
2592 # forward error messages to logfile
2593 if( ! $foreground ) {
2594   open( STDIN,  '+>/dev/null' );
2595   open( STDOUT, '+>&STDIN'    );
2596   open( STDERR, '+>&STDIN'    );
2599 # Just fork, if we are not in foreground mode
2600 if( ! $foreground ) { 
2601     chdir '/'                 or die "Can't chdir to /: $!";
2602     $pid = fork;
2603     setsid                    or die "Can't start a new session: $!";
2604     umask 0;
2605 } else { 
2606     $pid = $$; 
2609 # Do something useful - put our PID into the pid_file
2610 if( 0 != $pid ) {
2611     open( LOCK_FILE, ">$pid_file" );
2612     print LOCK_FILE "$pid\n";
2613     close( LOCK_FILE );
2614     if( !$foreground ) { 
2615         exit( 0 ) 
2616     };
2619 # parse head url and revision from svn
2620 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2621 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2622 $server_headURL = defined $1 ? $1 : 'unknown' ;
2623 $server_revision = defined $2 ? $2 : 'unknown' ;
2624 if ($server_headURL =~ /\/tag\// || 
2625         $server_headURL =~ /\/branches\// ) {
2626     $server_status = "stable"; 
2627 } else {
2628     $server_status = "developmental" ;
2632 daemon_log(" ", 1);
2633 daemon_log("$0 started!", 1);
2634 daemon_log("status: $server_status", 1);
2635 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2637 if ($no_bus > 0) {
2638     $bus_activ = "false"
2641 # connect to incoming_db
2642 unlink($incoming_file_name);
2643 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2644 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2646 # connect to gosa-si job queue
2647 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2648 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2650 # connect to known_clients_db
2651 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2652 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2654 # connect to known_server_db
2655 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2656 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2658 # connect to login_usr_db
2659 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2660 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2662 # connect to fai_server_db and fai_release_db
2663 unlink($fai_server_file_name);
2664 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2665 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2667 unlink($fai_release_file_name);
2668 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2669 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2671 # connect to packages_list_db
2672 #unlink($packages_list_file_name);
2673 unlink($packages_list_under_construction);
2674 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2675 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2677 # connect to messaging_db
2678 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2679 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2682 # create xml object used for en/decrypting
2683 $xml = new XML::Simple();
2685 # create socket for incoming xml messages
2687 POE::Component::Server::TCP->new(
2688         Port => $server_port,
2689         ClientInput => sub {
2690         my ($kernel, $input) = @_[KERNEL, ARG0];
2691         push(@tasks, $input);
2692         push(@msgs_to_decrypt, $input);
2693         $kernel->yield("next_task");
2694         $kernel->yield("msg_to_decrypt");
2695         },
2696     InlineStates => {
2697         next_task => \&next_task,
2698         msg_to_decrypt => \&msg_to_decrypt,
2699         task_result => \&handle_task_result,
2700         task_done   => \&handle_task_done,
2701         task_debug  => \&handle_task_debug,
2702         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2703     }
2704 );
2706 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2708 # create session for repeatedly checking the job queue for jobs
2709 POE::Session->create(
2710         inline_states => {
2711                 _start => \&_start,
2712                 sig_handler => \&sig_handler,
2713         watch_for_new_messages => \&watch_for_new_messages,
2714         watch_for_delivery_messages => \&watch_for_delivery_messages,
2715         watch_for_done_messages => \&watch_for_done_messages,
2716                 watch_for_new_jobs => \&watch_for_new_jobs,
2717         watch_for_done_jobs => \&watch_for_done_jobs,
2718         create_packages_list_db => \&run_create_packages_list_db,
2719         create_fai_server_db => \&run_create_fai_server_db,
2720         create_fai_release_db => \&run_create_fai_release_db,
2721         session_run_result => \&session_run_result,
2722         session_run_debug => \&session_run_debug,
2723         session_run_done => \&session_run_done,
2724         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2725         }
2726 );
2729 # import all modules
2730 &import_modules;
2732 # check wether all modules are gosa-si valid passwd check
2734 POE::Kernel->run();
2735 exit;