Code

bugfix: log file has permission 0600
[gosa.git] / gosa-si / gosa-si-server
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 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5  qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
55 my $server_version = '$HeadURL$:$Rev$';
56 my $server_headURL;
57 my $server_revision;
58 my $server_status;
61 # TODO es gibt eine globale funktion get_ldap_handle
62 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
63 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
64 # - sessions die kein ldap handle brauchen, sollen auch keins haben
65 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
66 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_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
100 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_bus = 0;
113 $bus_activ = "true";
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 our $prg= basename($0);
121 # holds all gosa jobs
122 our $job_db;
123 our $job_queue_tn = 'jobs';
124 my $job_queue_file_name;
125 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
126                 "timestamp DEFAULT 'none'", 
127                 "status DEFAULT 'none'", 
128                 "result DEFAULT 'none'", 
129                 "progress DEFAULT 'none'", 
130         "headertag DEFAULT 'none'", 
131                 "targettag DEFAULT 'none'", 
132                 "xmlmessage DEFAULT 'none'", 
133                 "macaddress DEFAULT 'none'",
134                 "plainname DEFAULT 'none'",
135                 );
137 # holds all other gosa-sd as well as the gosa-sd-bus
138 our $known_server_db;
139 our $known_server_tn = "known_server";
140 my $known_server_file_name;
141 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
143 # holds all registrated clients
144 our $known_clients_db;
145 our $known_clients_tn = "known_clients";
146 my $known_clients_file_name;
147 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
149 # holds all logged in user at each client 
150 our $login_users_db;
151 our $login_users_tn = "login_users";
152 my $login_users_file_name;
153 my @login_users_col_names = ("client", "user", "timestamp");
155 # holds all fai server, the debian release and tag
156 our $fai_server_db;
157 our $fai_server_tn = "fai_server"; 
158 my $fai_server_file_name;
159 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
161 our $fai_release_db;
162 our $fai_release_tn = "fai_release"; 
163 my $fai_release_file_name;
164 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
166 # holds all packages available from different repositories
167 our $packages_list_db;
168 our $packages_list_tn = "packages_list";
169 my $packages_list_file_name;
170 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
171 my $outdir = "/tmp/packages_list_db";
172 my $arch = "i386"; 
174 # holds all messages which should be delivered to a user
175 our $messaging_db;
176 our $messaging_tn = "messaging"; 
177 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
178         "flag", "direction", "delivery_time", "message", "timestamp" );
179 my $messaging_file_name;
181 # path to directory to store client install log files
182 our $client_fai_log_dir = "/var/log/fai"; 
184 # queue which stores taskes until one of the $max_children children are ready to process the task
185 my @tasks = qw();
186 my $max_children = 2;
189 %cfg_defaults = (
190 "general" => {
191     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
192     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
193     },
194 "bus" => {
195     "activ" => [\$bus_activ, "true"],
196     },
197 "server" => {
198     "port" => [\$server_port, "20081"],
199     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
200     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
201     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
202     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
203     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
204     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
205     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
206     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
207     "repo-path" => [\$repo_path, '/srv/www/repository'],
208     "ldap-uri" => [\$ldap_uri, ""],
209     "ldap-base" => [\$ldap_base, ""],
210     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
211     "ldap-admin-password" => [\$ldap_admin_password, ""],
212     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
213     "max-clients" => [\$max_clients, 10],
214     },
215 "GOsaPackages" => {
216     "ip" => [\$gosa_ip, "0.0.0.0"],
217     "port" => [\$gosa_port, "20082"],
218     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
219     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
220     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
221     "key" => [\$GosaPackages_key, "none"],
222     },
223 "SIPackages" => {
224     "key" => [\$SIPackages_key, "none"],
225     },
226 );
229 #===  FUNCTION  ================================================================
230 #         NAME:  usage
231 #   PARAMETERS:  nothing
232 #      RETURNS:  nothing
233 #  DESCRIPTION:  print out usage text to STDERR
234 #===============================================================================
235 sub usage {
236     print STDERR << "EOF" ;
237 usage: $prg [-hvf] [-c config]
239            -h        : this (help) message
240            -c <file> : config file
241            -f        : foreground, process will not be forked to background
242            -v        : be verbose (multiple to increase verbosity)
243            -no-bus   : starts $prg without connection to bus
244            -no-arp   : starts $prg without connection to arp module
245  
246 EOF
247     print "\n" ;
251 #===  FUNCTION  ================================================================
252 #         NAME:  read_configfile
253 #   PARAMETERS:  cfg_file - string -
254 #      RETURNS:  nothing
255 #  DESCRIPTION:  read cfg_file and set variables
256 #===============================================================================
257 sub read_configfile {
258     my $cfg;
259     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
260         if( -r $cfg_file ) {
261             $cfg = Config::IniFiles->new( -file => $cfg_file );
262         } else {
263             print STDERR "Couldn't read config file!\n";
264         }
265     } else {
266         $cfg = Config::IniFiles->new() ;
267     }
268     foreach my $section (keys %cfg_defaults) {
269         foreach my $param (keys %{$cfg_defaults{ $section }}) {
270             my $pinfo = $cfg_defaults{ $section }{ $param };
271             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
272         }
273     }
277 #===  FUNCTION  ================================================================
278 #         NAME:  logging
279 #   PARAMETERS:  level - string - default 'info'
280 #                msg - string -
281 #                facility - string - default 'LOG_DAEMON'
282 #      RETURNS:  nothing
283 #  DESCRIPTION:  function for logging
284 #===============================================================================
285 sub daemon_log {
286     # log into log_file
287     my( $msg, $level ) = @_;
288     if(not defined $msg) { return }
289     if(not defined $level) { $level = 1 }
290     if(defined $log_file){
291         open(LOG_HANDLE, ">>$log_file");
292         chmod 0600, $log_file;
293         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
294             print STDERR "cannot open $log_file: $!";
295             return }
296             chomp($msg);
297                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
298             if($level <= $verbose){
299                 my ($seconds, $minutes, $hours, $monthday, $month,
300                         $year, $weekday, $yearday, $sommertime) = localtime(time);
301                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
302                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
303                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
304                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
305                 $month = $monthnames[$month];
306                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
307                 $year+=1900;
308                 my $name = $prg;
310                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
311                 print LOG_HANDLE $log_msg;
312                 if( $foreground ) { 
313                     print STDERR $log_msg;
314                 }
315             }
316         close( LOG_HANDLE );
317     }
321 #===  FUNCTION  ================================================================
322 #         NAME:  check_cmdline_param
323 #   PARAMETERS:  nothing
324 #      RETURNS:  nothing
325 #  DESCRIPTION:  validates commandline parameter
326 #===============================================================================
327 sub check_cmdline_param () {
328     my $err_config;
329     my $err_counter = 0;
330         if(not defined($cfg_file)) {
331                 $cfg_file = "/etc/gosa-si/server.conf";
332                 if(! -r $cfg_file) {
333                         $err_config = "please specify a config file";
334                         $err_counter += 1;
335                 }
336     }
337     if( $err_counter > 0 ) {
338         &usage( "", 1 );
339         if( defined( $err_config)) { print STDERR "$err_config\n"}
340         print STDERR "\n";
341         exit( -1 );
342     }
346 #===  FUNCTION  ================================================================
347 #         NAME:  check_pid
348 #   PARAMETERS:  nothing
349 #      RETURNS:  nothing
350 #  DESCRIPTION:  handels pid processing
351 #===============================================================================
352 sub check_pid {
353     $pid = -1;
354     # Check, if we are already running
355     if( open(LOCK_FILE, "<$pid_file") ) {
356         $pid = <LOCK_FILE>;
357         if( defined $pid ) {
358             chomp( $pid );
359             if( -f "/proc/$pid/stat" ) {
360                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
361                 if( $stat ) {
362                                         daemon_log("ERROR: Already running",1);
363                     close( LOCK_FILE );
364                     exit -1;
365                 }
366             }
367         }
368         close( LOCK_FILE );
369         unlink( $pid_file );
370     }
372     # create a syslog msg if it is not to possible to open PID file
373     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
374         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
375         if (open(LOCK_FILE, '<', $pid_file)
376                 && ($pid = <LOCK_FILE>))
377         {
378             chomp($pid);
379             $msg .= "(PID $pid)\n";
380         } else {
381             $msg .= "(unable to read PID)\n";
382         }
383         if( ! ($foreground) ) {
384             openlog( $0, "cons,pid", "daemon" );
385             syslog( "warning", $msg );
386             closelog();
387         }
388         else {
389             print( STDERR " $msg " );
390         }
391         exit( -1 );
392     }
395 #===  FUNCTION  ================================================================
396 #         NAME:  import_modules
397 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
398 #                are stored
399 #      RETURNS:  nothing
400 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
401 #                state is on is imported by "require 'file';"
402 #===============================================================================
403 sub import_modules {
404     daemon_log(" ", 1);
406     if (not -e $modules_path) {
407         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
408     }
410     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
411     while (defined (my $file = readdir (DIR))) {
412         if (not $file =~ /(\S*?).pm$/) {
413             next;
414         }
415                 my $mod_name = $1;
417         if( $file =~ /ArpHandler.pm/ ) {
418             if( $no_arp > 0 ) {
419                 next;
420             }
421         }
422         
423         eval { require $file; };
424         if ($@) {
425             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
426             daemon_log("$@", 5);
427                 } else {
428                         my $info = eval($mod_name.'::get_module_info()');
429                         # Only load module if get_module_info() returns a non-null object
430                         if( $info ) {
431                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
432                                 $known_modules->{$mod_name} = $info;
433                                 daemon_log("INFO: module $mod_name loaded", 5);
434                         }
435                 }
436     }   
437     close (DIR);
441 #===  FUNCTION  ================================================================
442 #         NAME:  sig_int_handler
443 #   PARAMETERS:  signal - string - signal arose from system
444 #      RETURNS:  noting
445 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
446 #===============================================================================
447 sub sig_int_handler {
448     my ($signal) = @_;
450 #       if (defined($ldap_handle)) {
451 #               $ldap_handle->disconnect;
452 #       }
453     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
454     
456     daemon_log("shutting down gosa-si-server", 1);
457     system("kill `ps -C gosa-si-server -o pid=`");
459 $SIG{INT} = \&sig_int_handler;
462 sub check_key_and_xml_validity {
463     my ($crypted_msg, $module_key, $session_id) = @_;
464     my $msg;
465     my $msg_hash;
466     my $error_string;
467     eval{
468         $msg = &decrypt_msg($crypted_msg, $module_key);
470         if ($msg =~ /<xml>/i){
471             $msg =~ s/\s+/ /g;  # just for better daemon_log
472             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
473             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
475             ##############
476             # check header
477             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
478             my $header_l = $msg_hash->{'header'};
479             if( 1 > @{$header_l} ) { die 'empty header tag'; }
480             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
481             my $header = @{$header_l}[0];
482             if( 0 == length $header) { die 'empty string in header tag'; }
484             ##############
485             # check source
486             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
487             my $source_l = $msg_hash->{'source'};
488             if( 1 > @{$source_l} ) { die 'empty source tag'; }
489             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
490             my $source = @{$source_l}[0];
491             if( 0 == length $source) { die 'source error'; }
493             ##############
494             # check target
495             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
496             my $target_l = $msg_hash->{'target'};
497             if( 1 > @{$target_l} ) { die 'empty target tag'; }
498         }
499     };
500     if($@) {
501         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
502         $msg = undef;
503         $msg_hash = undef;
504     }
506     return ($msg, $msg_hash);
510 sub check_outgoing_xml_validity {
511     my ($msg) = @_;
513     my $msg_hash;
514     eval{
515         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
517         ##############
518         # check header
519         my $header_l = $msg_hash->{'header'};
520         if( 1 != @{$header_l} ) {
521             die 'no or more than one headers specified';
522         }
523         my $header = @{$header_l}[0];
524         if( 0 == length $header) {
525             die 'header has length 0';
526         }
528         ##############
529         # check source
530         my $source_l = $msg_hash->{'source'};
531         if( 1 != @{$source_l} ) {
532             die 'no or more than 1 sources specified';
533         }
534         my $source = @{$source_l}[0];
535         if( 0 == length $source) {
536             die 'source has length 0';
537         }
538         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
539                 $source =~ /^GOSA$/i ) {
540             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
541         }
542         
543         ##############
544         # check target  
545         my $target_l = $msg_hash->{'target'};
546         if( 0 == @{$target_l} ) {
547             die "no targets specified";
548         }
549         foreach my $target (@$target_l) {
550             if( 0 == length $target) {
551                 die "target has length 0";
552             }
553             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
554                     $target =~ /^GOSA$/i ||
555                     $target =~ /^\*$/ ||
556                     $target =~ /KNOWN_SERVER/i ||
557                     $target =~ /JOBDB/i ||
558                     $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 ){
559                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
560             }
561         }
562     };
563     if($@) {
564         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
565         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
566         $msg_hash = undef;
567     }
569     return ($msg_hash);
573 sub input_from_known_server {
574     my ($input, $remote_ip, $session_id) = @_ ;  
575     my ($msg, $msg_hash, $module);
577     my $sql_statement= "SELECT * FROM known_server";
578     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
580     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
581         my $host_name = $hit->{hostname};
582         if( not $host_name =~ "^$remote_ip") {
583             next;
584         }
585         my $host_key = $hit->{hostkey};
586         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
587         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
589         # check if module can open msg envelope with module key
590         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
591         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
592             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
593             daemon_log("$@", 8);
594             next;
595         }
596         else {
597             $msg = $tmp_msg;
598             $msg_hash = $tmp_msg_hash;
599             $module = "SIPackages";
600             last;
601         }
602     }
604     if( (!$msg) || (!$msg_hash) || (!$module) ) {
605         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
606     }
607   
608     return ($msg, $msg_hash, $module);
612 sub input_from_known_client {
613     my ($input, $remote_ip, $session_id) = @_ ;  
614     my ($msg, $msg_hash, $module);
616     my $sql_statement= "SELECT * FROM known_clients";
617     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
618     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
619         my $host_name = $hit->{hostname};
620         if( not $host_name =~ /^$remote_ip:\d*$/) {
621                 next;
622                 }
623         my $host_key = $hit->{hostkey};
624         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
625         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
627         # check if module can open msg envelope with module key
628         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
630         if( (!$msg) || (!$msg_hash) ) {
631             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
632             &daemon_log("$@", 8);
633             next;
634         }
635         else {
636             $module = "SIPackages";
637             last;
638         }
639     }
641     if( (!$msg) || (!$msg_hash) || (!$module) ) {
642         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
643     }
645     return ($msg, $msg_hash, $module);
649 sub input_from_unknown_host {
650     no strict "refs";
651     my ($input, $session_id) = @_ ;
652     my ($msg, $msg_hash, $module);
653     my $error_string;
654     
655         my %act_modules = %$known_modules;
657         while( my ($mod, $info) = each(%act_modules)) {
659         # check a key exists for this module
660         my $module_key = ${$mod."_key"};
661         if( not defined $module_key ) {
662             if( $mod eq 'ArpHandler' ) {
663                 next;
664             }
665             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
666             next;
667         }
668         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
670         # check if module can open msg envelope with module key
671         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
672         if( (not defined $msg) || (not defined $msg_hash) ) {
673             next;
674         }
675         else {
676             $module = $mod;
677             last;
678         }
679     }
681     if( (!$msg) || (!$msg_hash) || (!$module)) {
682         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
683     }
685     return ($msg, $msg_hash, $module);
689 sub create_ciphering {
690     my ($passwd) = @_;
691         if((!defined($passwd)) || length($passwd)==0) {
692                 $passwd = "";
693         }
694     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
695     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
696     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
697     $my_cipher->set_iv($iv);
698     return $my_cipher;
702 sub encrypt_msg {
703     my ($msg, $key) = @_;
704     my $my_cipher = &create_ciphering($key);
705     my $len;
706     {
707             use bytes;
708             $len= 16-length($msg)%16;
709     }
710     $msg = "\0"x($len).$msg;
711     $msg = $my_cipher->encrypt($msg);
712     chomp($msg = &encode_base64($msg));
713     # there are no newlines allowed inside msg
714     $msg=~ s/\n//g;
715     return $msg;
719 sub decrypt_msg {
721     my ($msg, $key) = @_ ;
722     $msg = &decode_base64($msg);
723     my $my_cipher = &create_ciphering($key);
724     $msg = $my_cipher->decrypt($msg); 
725     $msg =~ s/\0*//g;
726     return $msg;
730 sub get_encrypt_key {
731     my ($target) = @_ ;
732     my $encrypt_key;
733     my $error = 0;
735     # target can be in known_server
736     if( not defined $encrypt_key ) {
737         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
738         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
739         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
740             my $host_name = $hit->{hostname};
741             if( $host_name ne $target ) {
742                 next;
743             }
744             $encrypt_key = $hit->{hostkey};
745             last;
746         }
747     }
749     # target can be in known_client
750     if( not defined $encrypt_key ) {
751         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
752         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
753         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
754             my $host_name = $hit->{hostname};
755             if( $host_name ne $target ) {
756                 next;
757             }
758             $encrypt_key = $hit->{hostkey};
759             last;
760         }
761     }
763     return $encrypt_key;
767 #===  FUNCTION  ================================================================
768 #         NAME:  open_socket
769 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
770 #                [PeerPort] string necessary if port not appended by PeerAddr
771 #      RETURNS:  socket IO::Socket::INET
772 #  DESCRIPTION:  open a socket to PeerAddr
773 #===============================================================================
774 sub open_socket {
775     my ($PeerAddr, $PeerPort) = @_ ;
776     if(defined($PeerPort)){
777         $PeerAddr = $PeerAddr.":".$PeerPort;
778     }
779     my $socket;
780     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
781             Porto => "tcp",
782             Type => SOCK_STREAM,
783             Timeout => 5,
784             );
785     if(not defined $socket) {
786         return;
787     }
788 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
789     return $socket;
793 #===  FUNCTION  ================================================================
794 #         NAME:  get_ip 
795 #   PARAMETERS:  interface name (i.e. eth0)
796 #      RETURNS:  (ip address) 
797 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
798 #===============================================================================
799 sub get_ip {
800         my $ifreq= shift;
801         my $result= "";
802         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
803         my $proto= getprotobyname('ip');
805         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
806                 or die "socket: $!";
808         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
809                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
810                 my ($port, $addr) = sockaddr_in $sin;
811                 my $ip            = inet_ntoa $addr;
813                 if ($ip && length($ip) > 0) {
814                         $result = $ip;
815                 }
816         }
818         return $result;
822 sub get_local_ip_for_remote_ip {
823         my $remote_ip= shift;
824         my $result="0.0.0.0";
826         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
827                 if($remote_ip eq "127.0.0.1") {
828                         $result = "127.0.0.1";
829                 } else {
830                         my $PROC_NET_ROUTE= ('/proc/net/route');
832                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
833                                 or die "Could not open $PROC_NET_ROUTE";
835                         my @ifs = <PROC_NET_ROUTE>;
837                         close(PROC_NET_ROUTE);
839                         # Eat header line
840                         shift @ifs;
841                         chomp @ifs;
842                         foreach my $line(@ifs) {
843                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
844                                 my $destination;
845                                 my $mask;
846                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
847                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
848                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
849                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
850                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
851                                         # destination matches route, save mac and exit
852                                         $result= &get_ip($Iface);
853                                         last;
854                                 }
855                         }
856                 }
857         } else {
858                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
859         }
860         return $result;
864 sub send_msg_to_target {
865     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
866     my $error = 0;
867     my $header;
868     my $new_status;
869     my $act_status;
870     my ($sql_statement, $res);
871   
872     if( $msg_header ) {
873         $header = "'$msg_header'-";
874     } else {
875         $header = "";
876     }
878         # Patch the source ip
879         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
880                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
881                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
882         }
884     # encrypt xml msg
885     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
887     # opensocket
888     my $socket = &open_socket($address);
889     if( !$socket ) {
890         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
891         $error++;
892     }
893     
894     if( $error == 0 ) {
895         # send xml msg
896         print $socket $crypted_msg."\n";
898         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
899         #daemon_log("DEBUG: message:\n$msg", 9);
900         
901     }
903     # close socket in any case
904     if( $socket ) {
905         close $socket;
906     }
908     if( $error > 0 ) { $new_status = "down"; }
909     else { $new_status = $msg_header; }
912     # known_clients
913     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
914     $res = $known_clients_db->select_dbentry($sql_statement);
915     if( keys(%$res) > 0) {
916         $act_status = $res->{1}->{'status'};
917         if( $act_status eq "down" ) {
918             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
919             $res = $known_clients_db->del_dbentry($sql_statement);
920             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
921         } else { 
922             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
923             $res = $known_clients_db->update_dbentry($sql_statement);
924             if($new_status eq "down"){
925                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
926             } else {
927                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
928             }
929         }
930     }
932     # known_server
933     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
934     $res = $known_server_db->select_dbentry($sql_statement);
935     if( keys(%$res) > 0 ) {
936         $act_status = $res->{1}->{'status'};
937         if( $act_status eq "down" ) {
938             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
939             $res = $known_server_db->del_dbentry($sql_statement);
940             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
941         } 
942         else { 
943             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
944             $res = $known_server_db->update_dbentry($sql_statement);
945             if($new_status eq "down"){
946                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
947             }
948             else {
949                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
950             }
951         }
952     }
953     return $error; 
957 sub update_jobdb_status_for_send_msgs {
958     my ($answer, $error) = @_;
959     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
960         my $jobdb_id = $1;
961             
962         # sending msg faild
963         if( $error ) {
964             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
965                 my $sql_statement = "UPDATE $job_queue_tn ".
966                     "SET status='error', result='can not deliver msg, please consult log file' ".
967                     "WHERE id=$jobdb_id";
968                 my $res = $job_db->update_dbentry($sql_statement);
969             }
971         # sending msg was successful
972         } else {
973             my $sql_statement = "UPDATE $job_queue_tn ".
974                 "SET status='done' ".
975                 "WHERE id=$jobdb_id AND status='processed'";
976             my $res = $job_db->update_dbentry($sql_statement);
977         }
978     }
981 sub _start {
982     my ($kernel) = $_[KERNEL];
983     &trigger_db_loop($kernel);
984     $global_kernel = $kernel;
985         $kernel->yield('create_fai_server_db', $fai_server_tn );
986         $kernel->yield('create_fai_release_db', $fai_release_tn );
987         $kernel->sig(USR1 => "sig_handler");
988         $kernel->sig(USR2 => "create_packages_list_db");
991 sub sig_handler {
992         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
993         daemon_log("0 INFO got signal '$signal'", 1); 
994         $kernel->sig_handled();
995         return;
998 sub next_task {
999     my ($session, $heap) = @_[SESSION, HEAP];
1001     while ( keys( %{ $heap->{task} } ) < $max_children ) {
1002         my $next_task = shift @tasks;
1003         last unless defined $next_task;
1005         my $task = POE::Wheel::Run->new(
1006                 Program => sub { process_task($session, $heap, $next_task) },
1007                 StdioFilter => POE::Filter::Reference->new(),
1008                 StdoutEvent  => "task_result",
1009                 StderrEvent  => "task_debug",
1010                 CloseEvent   => "task_done",
1011                );
1013         $heap->{task}->{ $task->ID } = $task;
1014     }
1017 sub handle_task_result {
1018     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1019     my $client_answer = $result->{'answer'};
1020     if( $client_answer =~ s/session_id=(\d+)$// ) {
1021         my $session_id = $1;
1022         if( defined $session_id ) {
1023             my $session_reference = $kernel->ID_id_to_session($session_id);
1024             if( defined $session_reference ) {
1025                 $heap = $session_reference->get_heap();
1026             }
1027         }
1029         if(exists $heap->{'client'}) {
1030             $heap->{'client'}->put($client_answer);
1031         }
1032     }
1033     $kernel->sig(CHLD => "child_reap");
1036 sub handle_task_debug {
1037     my $result = $_[ARG0];
1038     print STDERR "$result\n";
1041 sub handle_task_done {
1042     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1043     delete $heap->{task}->{$task_id};
1044     $kernel->yield("next_task");
1047 sub process_task {
1048     no strict "refs";
1049     my ($session, $heap, $input) = @_;
1050     my $session_id = $session->ID;
1051     my ($msg, $msg_hash, $module);
1052     my $error = 0;
1053     my $answer_l;
1054     my ($answer_header, @answer_target_l, $answer_source);
1055     my $client_answer = "";
1057     daemon_log("", 5); 
1058     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1059     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1061     ####################
1062     # check incoming msg
1063     # msg is from a new client or gosa
1064     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1065     # msg is from a gosa-si-server or gosa-si-bus
1066     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1067         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1068     }
1069     # msg is from a gosa-si-client
1070     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1071         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1072     }
1073     # an error occurred
1074     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1075         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1076         # could not understand a msg from its server the client cause a re-registering process
1077         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);
1078         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1079         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1080         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1081             my $host_name = $hit->{'hostname'};
1082             my $host_key = $hit->{'hostkey'};
1083             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1084             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1085             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1086         }
1087         $error++;
1088     }
1090     ######################
1091     # process incoming msg
1092     if( $error == 0) {
1093         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1094                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1095         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1096         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1098         if ( 0 < @{$answer_l} ) {
1099             my $answer_str = join("\n", @{$answer_l});
1100             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1101         } else {
1102             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1103         }
1105     }
1106     if( !$answer_l ) { $error++ };
1108     ########
1109     # answer
1110     if( $error == 0 ) {
1112         foreach my $answer ( @{$answer_l} ) {
1113             # for each answer in answer list
1114             
1115             # check outgoing msg to xml validity
1116             my $answer_hash = &check_outgoing_xml_validity($answer);
1117             if( not defined $answer_hash ) {
1118                 next;
1119             }
1120             
1121             $answer_header = @{$answer_hash->{'header'}}[0];
1122             @answer_target_l = @{$answer_hash->{'target'}};
1123             $answer_source = @{$answer_hash->{'source'}}[0];
1125             # deliver msg to all targets 
1126             foreach my $answer_target ( @answer_target_l ) {
1128                 # targets of msg are all gosa-si-clients in known_clients_db
1129                 if( $answer_target eq "*" ) {
1130                     # answer is for all clients
1131                     my $sql_statement= "SELECT * FROM known_clients";
1132                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1133                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1134                         my $host_name = $hit->{hostname};
1135                         my $host_key = $hit->{hostkey};
1136                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1137                         &update_jobdb_status_for_send_msgs($answer, $error);
1138                     }
1139                 }
1141                 # targets of msg are all gosa-si-server in known_server_db
1142                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1143                     # answer is for all server in known_server
1144                     my $sql_statement= "SELECT * FROM known_server";
1145                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1146                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1147                         my $host_name = $hit->{hostname};
1148                         my $host_key = $hit->{hostkey};
1149                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1150                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1151                         &update_jobdb_status_for_send_msgs($answer, $error);
1152                     }
1153                 }
1155                 # target of msg is GOsa
1156                                 elsif( $answer_target eq "GOSA" ) {
1157                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1158                                         my $add_on = "";
1159                     if( defined $session_id ) {
1160                         $add_on = ".session_id=$session_id";
1161                     }
1162                     # answer is for GOSA and has to returned to connected client
1163                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1164                     $client_answer = $gosa_answer.$add_on;
1165                 }
1167                 # target of msg is job queue at this host
1168                 elsif( $answer_target eq "JOBDB") {
1169                     $answer =~ /<header>(\S+)<\/header>/;   
1170                     my $header;
1171                     if( defined $1 ) { $header = $1; }
1172                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1173                     &update_jobdb_status_for_send_msgs($answer, $error);
1174                 }
1176                 # target of msg is a mac address
1177                 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 ) {
1178                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1179                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1180                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1181                     my $found_ip_flag = 0;
1182                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1183                         my $host_name = $hit->{hostname};
1184                         my $host_key = $hit->{hostkey};
1185                         $answer =~ s/$answer_target/$host_name/g;
1186                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1187                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1188                         &update_jobdb_status_for_send_msgs($answer, $error);
1189                         $found_ip_flag++ ;
1190                     }   
1191                     if( $found_ip_flag == 0) {
1192                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1193                         if( $bus_activ eq "true" ) { 
1194                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1195                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1196                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1197                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1198                                 my $bus_address = $hit->{hostname};
1199                                 my $bus_key = $hit->{hostkey};
1200                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1201                                 &update_jobdb_status_for_send_msgs($answer, $error);
1202                                 last;
1203                             }
1204                         }
1206                     }
1208                 #  answer is for one specific host   
1209                 } else {
1210                     # get encrypt_key
1211                     my $encrypt_key = &get_encrypt_key($answer_target);
1212                     if( not defined $encrypt_key ) {
1213                         # unknown target, forward msg to bus
1214                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1215                         if( $bus_activ eq "true" ) { 
1216                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1217                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1218                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1219                             my $res_length = keys( %{$query_res} );
1220                             if( $res_length == 0 ){
1221                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1222                                         "no bus found in known_server", 3);
1223                             }
1224                             else {
1225                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1226                                     my $bus_key = $hit->{hostkey};
1227                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1228                                     &update_jobdb_status_for_send_msgs($answer, $error);
1229                                 }
1230                             }
1231                         }
1232                         next;
1233                     }
1234                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1235                     &update_jobdb_status_for_send_msgs($answer, $error);
1236                 }
1237             }
1238         }
1239     }
1241     my $filter = POE::Filter::Reference->new();
1242     my %result = ( 
1243             status => "seems ok to me",
1244             answer => $client_answer,
1245             );
1247     my $output = $filter->put( [ \%result ] );
1248     print @$output;
1254 sub trigger_db_loop {
1255         my ($kernel) = @_ ;
1256         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1257         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1258         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1259     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1260         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1264 sub watch_for_done_jobs {
1265     my ($kernel,$heap) = @_[KERNEL, HEAP];
1267     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1268         " WHERE status='done'";
1269         my $res = $job_db->select_dbentry( $sql_statement );
1271     while( my ($id, $hit) = each %{$res} ) {
1272         my $jobdb_id = $hit->{id};
1273         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1274         my $res = $job_db->del_dbentry($sql_statement); 
1275     }
1277     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1281 sub watch_for_new_jobs {
1282         if($watch_for_new_jobs_in_progress == 0) {
1283                 $watch_for_new_jobs_in_progress = 1;
1284                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1286                 # check gosa job queue for jobs with executable timestamp
1287                 my $timestamp = &get_time();
1288                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1289                 my $res = $job_db->exec_statement( $sql_statement );
1291                 # Merge all new jobs that would do the same actions
1292                 my @drops;
1293                 my $hits;
1294                 foreach my $hit (reverse @{$res} ) {
1295                         my $macaddress= lc @{$hit}[8];
1296                         my $headertag= @{$hit}[5];
1297                         if(
1298                                 defined($hits->{$macaddress}) &&
1299                                 defined($hits->{$macaddress}->{$headertag}) &&
1300                                 defined($hits->{$macaddress}->{$headertag}[0])
1301                         ) {
1302                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1303                         }
1304                         $hits->{$macaddress}->{$headertag}= $hit;
1305                 }
1307                 # Delete new jobs with a matching job in state 'processing'
1308                 foreach my $macaddress (keys %{$hits}) {
1309                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1310                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1311                                 if(defined($jobdb_id)) {
1312                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1313                                         my $res = $job_db->exec_statement( $sql_statement );
1314                                         foreach my $hit (@{$res}) {
1315                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1316                                         }
1317                                 } else {
1318                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1319                                 }
1320                         }
1321                 }
1323                 # Commit deletion
1324                 $job_db->exec_statementlist(\@drops);
1326                 # Look for new jobs that could be executed
1327                 foreach my $macaddress (keys %{$hits}) {
1329                         # Look if there is an executing job
1330                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1331                         my $res = $job_db->exec_statement( $sql_statement );
1333                         # Skip new jobs for host if there is a processing job
1334                         if(defined($res) and defined @{$res}[0]) {
1335                                 next;
1336                         }
1338                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1339                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1340                                 if(defined($jobdb_id)) {
1341                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1343                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1344                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1345                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1347                                         # expect macaddress is unique!!!!!!
1348                                         my $target = $res_hash->{1}->{hostname};
1350                                         # change header
1351                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1353                                         # add sqlite_id
1354                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1356                                         $job_msg =~ /<header>(\S+)<\/header>/;
1357                                         my $header = $1 ;
1358                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1360                                         # update status in job queue to 'processing'
1361                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1362                                         my $res = $job_db->update_dbentry($sql_statement);
1363 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1365                                         # We don't want parallel processing
1366                                         last;
1367                                 }
1368                         }
1369                 }
1371                 $watch_for_new_jobs_in_progress = 0;
1372                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1373         }
1377 sub watch_for_new_messages {
1378     my ($kernel,$heap) = @_[KERNEL, HEAP];
1379     my @coll_user_msg;   # collection list of outgoing messages
1380     
1381     # check messaging_db for new incoming messages with executable timestamp
1382     my $timestamp = &get_time();
1383     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1384     my $res = $messaging_db->exec_statement( $sql_statement );
1385         foreach my $hit (@{$res}) {
1387         # create outgoing messages
1388         my $message_to = @{$hit}[3];
1389         # translate message_to to plain login name
1390         my @message_to_l = split(/,/, $message_to);  
1391                 my %receiver_h; 
1392                 foreach my $receiver (@message_to_l) {
1393                         if ($receiver =~ /^u_([\s\S]*)$/) {
1394                                 $receiver_h{$1} = 0;
1395                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1396 # TODO implement receiver translation
1397                         } else {
1398                                 my $sbjct = &encode_base64(@{$hit}[1]);
1399                                 my $msg = &encode_base64(@{$hit}[7]);
1400                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message 'sbjct - msg'", 3); 
1401                         }
1402                 }
1403                 my @receiver_l = keys(%receiver_h);
1405         my $message_id = @{$hit}[0];
1407         #add each outgoing msg to messaging_db
1408         my $receiver;
1409         foreach $receiver (@receiver_l) {
1410             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1411                 "VALUES ('".
1412                 $message_id."', '".    # id
1413                 @{$hit}[1]."', '".     # subject
1414                 @{$hit}[2]."', '".     # message_from
1415                 $receiver."', '".      # message_to
1416                 "none"."', '".         # flag
1417                 "out"."', '".          # direction
1418                 @{$hit}[6]."', '".     # delivery_time
1419                 @{$hit}[7]."', '".     # message
1420                 $timestamp."'".     # timestamp
1421                 ")";
1422             &daemon_log("M DEBUG: $sql_statement", 1);
1423             my $res = $messaging_db->exec_statement($sql_statement);
1424             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1425         }
1427         # set incoming message to flag d=deliverd
1428         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1429         &daemon_log("M DEBUG: $sql_statement", 7);
1430         $res = $messaging_db->update_dbentry($sql_statement);
1431         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1432     }
1434     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1435     return;
1438 sub watch_for_delivery_messages {
1439     my ($kernel, $heap) = @_[KERNEL, HEAP];
1441     # select outgoing messages
1442     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1443     #&daemon_log("0 DEBUG: $sql", 7);
1444     my $res = $messaging_db->exec_statement( $sql_statement );
1445     
1446     # build out msg for each    usr
1447     foreach my $hit (@{$res}) {
1448         my $receiver = @{$hit}[3];
1449         my $msg_id = @{$hit}[0];
1450         my $subject = @{$hit}[1];
1451         my $message = @{$hit}[7];
1453         # resolve usr -> host where usr is logged in
1454         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1455         #&daemon_log("0 DEBUG: $sql", 7);
1456         my $res = $login_users_db->exec_statement($sql);
1458         # reciver is logged in nowhere
1459         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1461                 my $send_succeed = 0;
1462                 foreach my $hit (@$res) {
1463                                 my $receiver_host = @$hit[0];
1464                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1466                                 # fetch key to encrypt msg propperly for usr/host
1467                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1468                                 &daemon_log("0 DEBUG: $sql", 7);
1469                                 my $res = $known_clients_db->exec_statement($sql);
1471                                 # host is already down
1472                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1474                                 # host is on
1475                                 my $receiver_key = @{@{$res}[0]}[2];
1476                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1477                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1478                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1479                                 if ($error == 0 ) {
1480                                         $send_succeed++ ;
1481                                 }
1482                 }
1484                 if ($send_succeed) {
1485                                 # set outgoing msg at db to deliverd
1486                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1487                                 &daemon_log("0 DEBUG: $sql", 7);
1488                                 my $res = $messaging_db->exec_statement($sql); 
1489                 }
1490         }
1492     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1493     return;
1497 sub watch_for_done_messages {
1498     my ($kernel,$heap) = @_[KERNEL, HEAP];
1500     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1501     #&daemon_log("0 DEBUG: $sql", 7);
1502     my $res = $messaging_db->exec_statement($sql); 
1504     foreach my $hit (@{$res}) {
1505         my $msg_id = @{$hit}[0];
1507         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1508         #&daemon_log("0 DEBUG: $sql", 7); 
1509         my $res = $messaging_db->exec_statement($sql);
1511         # not all usr msgs have been seen till now
1512         if ( ref(@$res[0]) eq "ARRAY") { next; }
1513         
1514         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1515         #&daemon_log("0 DEBUG: $sql", 7);
1516         $res = $messaging_db->exec_statement($sql);
1517     
1518     }
1520     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1521     return;
1525 sub get_ldap_handle {
1526         my ($session_id) = @_;
1527         my $heap;
1528         my $ldap_handle;
1530         if (not defined $session_id ) { $session_id = 0 };
1531         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1533         if ($session_id == 0) {
1534                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1535                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1536                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1538         } else {
1539                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1540                 if( defined $session_reference ) {
1541                         $heap = $session_reference->get_heap();
1542                 }
1544                 if (not defined $heap) {
1545                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1546                         return;
1547                 }
1549                 # TODO: This "if" is nonsense, because it doesn't prove that the
1550                 #       used handle is still valid - or if we've to reconnect...
1551                 #if (not exists $heap->{ldap_handle}) {
1552                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1553                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1554                         $heap->{ldap_handle} = $ldap_handle;
1555                 #}
1556         }
1557         return $ldap_handle;
1561 sub change_fai_state {
1562     my ($st, $targets, $session_id) = @_;
1563     $session_id = 0 if not defined $session_id;
1564     # Set FAI state to localboot
1565     my %mapActions= (
1566         reboot    => '',
1567         update    => 'softupdate',
1568         localboot => 'localboot',
1569         reinstall => 'install',
1570         rescan    => '',
1571         wake      => '',
1572         memcheck  => 'memcheck',
1573         sysinfo   => 'sysinfo',
1574         install   => 'install',
1575     );
1577     # Return if this is unknown
1578     if (!exists $mapActions{ $st }){
1579         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1580       return;
1581     }
1583     my $state= $mapActions{ $st };
1585     my $ldap_handle = &get_ldap_handle($session_id);
1586     if( defined($ldap_handle) ) {
1588       # Build search filter for hosts
1589         my $search= "(&(objectClass=GOhard)";
1590         foreach (@{$targets}){
1591             $search.= "(macAddress=$_)";
1592         }
1593         $search.= ")";
1595       # If there's any host inside of the search string, procress them
1596         if (!($search =~ /macAddress/)){
1597             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1598             return;
1599         }
1601       # Perform search for Unit Tag
1602       my $mesg = $ldap_handle->search(
1603           base   => $ldap_base,
1604           scope  => 'sub',
1605           attrs  => ['dn', 'FAIstate', 'objectClass'],
1606           filter => "$search"
1607           );
1609           if ($mesg->count) {
1610                   my @entries = $mesg->entries;
1611                   foreach my $entry (@entries) {
1612                           # Only modify entry if it is not set to '$state'
1613                           if ($entry->get_value("FAIstate") ne "$state"){
1614                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1615                                   my $result;
1616                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1617                                   if (exists $tmp{'FAIobject'}){
1618                                           if ($state eq ''){
1619                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1620                                                           delete => [ FAIstate => [] ] ]);
1621                                           } else {
1622                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1623                                                           replace => [ FAIstate => $state ] ]);
1624                                           }
1625                                   } elsif ($state ne ''){
1626                                           $result= $ldap_handle->modify($entry->dn, changes => [
1627                                                   add     => [ objectClass => 'FAIobject' ],
1628                                                   add     => [ FAIstate => $state ] ]);
1629                                   }
1631                                   # Errors?
1632                                   if ($result->code){
1633                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1634                                   }
1635                           } else {
1636                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1637                           }  
1638                   }
1639           }
1640     # if no ldap handle defined
1641     } else {
1642         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1643     }
1648 sub change_goto_state {
1649     my ($st, $targets, $session_id) = @_;
1650     $session_id = 0  if not defined $session_id;
1652     # Switch on or off?
1653     my $state= $st eq 'active' ? 'active': 'locked';
1655     my $ldap_handle = &get_ldap_handle($session_id);
1656     if( defined($ldap_handle) ) {
1658       # Build search filter for hosts
1659       my $search= "(&(objectClass=GOhard)";
1660       foreach (@{$targets}){
1661         $search.= "(macAddress=$_)";
1662       }
1663       $search.= ")";
1665       # If there's any host inside of the search string, procress them
1666       if (!($search =~ /macAddress/)){
1667         return;
1668       }
1670       # Perform search for Unit Tag
1671       my $mesg = $ldap_handle->search(
1672           base   => $ldap_base,
1673           scope  => 'sub',
1674           attrs  => ['dn', 'gotoMode'],
1675           filter => "$search"
1676           );
1678       if ($mesg->count) {
1679         my @entries = $mesg->entries;
1680         foreach my $entry (@entries) {
1682           # Only modify entry if it is not set to '$state'
1683           if ($entry->get_value("gotoMode") ne $state){
1685             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1686             my $result;
1687             $result= $ldap_handle->modify($entry->dn, changes => [
1688                                                 replace => [ gotoMode => $state ] ]);
1690             # Errors?
1691             if ($result->code){
1692               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1693             }
1695           }
1696         }
1697       }
1699     }
1703 sub run_create_fai_server_db {
1704     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1705     my $session_id = $session->ID;
1706     my $task = POE::Wheel::Run->new(
1707             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1708             StdoutEvent  => "session_run_result",
1709             StderrEvent  => "session_run_debug",
1710             CloseEvent   => "session_run_done",
1711             );
1713     $heap->{task}->{ $task->ID } = $task;
1714     return;
1718 sub create_fai_server_db {
1719     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1720         my $result;
1722         if (not defined $session_id) { $session_id = 0; }
1723     my $ldap_handle = &get_ldap_handle();
1724         if(defined($ldap_handle)) {
1725                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1726                 my $mesg= $ldap_handle->search(
1727                         base   => $ldap_base,
1728                         scope  => 'sub',
1729                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1730                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1731                 );
1732                 if($mesg->{'resultCode'} == 0 &&
1733                    $mesg->count != 0) {
1734                    foreach my $entry (@{$mesg->{entries}}) {
1735                            if($entry->exists('FAIrepository')) {
1736                                    # Add an entry for each Repository configured for server
1737                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1738                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1739                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1740                                                    $result= $fai_server_db->add_dbentry( { 
1741                                                                    table => $table_name,
1742                                                                    primkey => ['server', 'release', 'tag'],
1743                                                                    server => $tmp_url,
1744                                                                    release => $tmp_release,
1745                                                                    sections => $tmp_sections,
1746                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1747                                                            } );
1748                                            }
1749                                    }
1750                            }
1751                    }
1752                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1754                 # TODO: Find a way to post the 'create_packages_list_db' event
1755                 if(not defined($dont_create_packages_list)) {
1756                         &create_packages_list_db(undef, undef, $session_id);
1757                 }
1758         }       
1759     
1760     $ldap_handle->disconnect;
1761         return $result;
1765 sub run_create_fai_release_db {
1766     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1767         my $session_id = $session->ID;
1768     my $task = POE::Wheel::Run->new(
1769             Program => sub { &create_fai_release_db($table_name, $session_id) },
1770             StdoutEvent  => "session_run_result",
1771             StderrEvent  => "session_run_debug",
1772             CloseEvent   => "session_run_done",
1773             );
1775     $heap->{task}->{ $task->ID } = $task;
1776     return;
1780 sub create_fai_release_db {
1781         my ($table_name, $session_id) = @_;
1782         my $result;
1784     # used for logging
1785     if (not defined $session_id) { $session_id = 0; }
1787     my $ldap_handle = &get_ldap_handle();
1788         if(defined($ldap_handle)) {
1789                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1790                 my $mesg= $ldap_handle->search(
1791                         base   => $ldap_base,
1792                         scope  => 'sub',
1793                         attrs  => [],
1794                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1795                 );
1796                 if($mesg->{'resultCode'} == 0 &&
1797                         $mesg->count != 0) {
1798                         # Walk through all possible FAI container ou's
1799                         my @sql_list;
1800                         my $timestamp= &get_time();
1801                         foreach my $ou (@{$mesg->{entries}}) {
1802                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1803                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1804                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1805                                         if(@tmp_array) {
1806                                                 foreach my $entry (@tmp_array) {
1807                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1808                                                                 my $sql= 
1809                                                                 "INSERT INTO $table_name "
1810                                                                 ."(timestamp, release, class, type, state) VALUES ("
1811                                                                 .$timestamp.","
1812                                                                 ."'".$entry->{'release'}."',"
1813                                                                 ."'".$entry->{'class'}."',"
1814                                                                 ."'".$entry->{'type'}."',"
1815                                                                 ."'".$entry->{'state'}."')";
1816                                                                 push @sql_list, $sql;
1817                                                         }
1818                                                 }
1819                                         }
1820                                 }
1821                         }
1823                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1824                         if(@sql_list) {
1825                                 unshift @sql_list, "VACUUM";
1826                                 unshift @sql_list, "DELETE FROM $table_name";
1827                                 $fai_release_db->exec_statementlist(\@sql_list);
1828                         }
1829                         daemon_log("$session_id DEBUG: Done with inserting",7);
1830                 }
1831                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1832         }
1833     $ldap_handle->disconnect;
1834         return $result;
1837 sub get_fai_types {
1838         my $tmp_classes = shift || return undef;
1839         my @result;
1841         foreach my $type(keys %{$tmp_classes}) {
1842                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1843                         my $entry = {
1844                                 type => $type,
1845                                 state => $tmp_classes->{$type}[0],
1846                         };
1847                         push @result, $entry;
1848                 }
1849         }
1851         return @result;
1854 sub get_fai_state {
1855         my $result = "";
1856         my $tmp_classes = shift || return $result;
1858         foreach my $type(keys %{$tmp_classes}) {
1859                 if(defined($tmp_classes->{$type}[0])) {
1860                         $result = $tmp_classes->{$type}[0];
1861                         
1862                 # State is equal for all types in class
1863                         last;
1864                 }
1865         }
1867         return $result;
1870 sub resolve_fai_classes {
1871         my ($fai_base, $ldap_handle, $session_id) = @_;
1872         if (not defined $session_id) { $session_id = 0; }
1873         my $result;
1874         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1875         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1876         my $fai_classes;
1878         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1879         my $mesg= $ldap_handle->search(
1880                 base   => $fai_base,
1881                 scope  => 'sub',
1882                 attrs  => ['cn','objectClass','FAIstate'],
1883                 filter => $fai_filter,
1884         );
1885         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1887         if($mesg->{'resultCode'} == 0 &&
1888                 $mesg->count != 0) {
1889                 foreach my $entry (@{$mesg->{entries}}) {
1890                         if($entry->exists('cn')) {
1891                                 my $tmp_dn= $entry->dn();
1893                                 # Skip classname and ou dn parts for class
1894                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1896                                 # Skip classes without releases
1897                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1898                                         next;
1899                                 }
1901                                 my $tmp_cn= $entry->get_value('cn');
1902                                 my $tmp_state= $entry->get_value('FAIstate');
1904                                 my $tmp_type;
1905                                 # Get FAI type
1906                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1907                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1908                                                 $tmp_type= $oclass;
1909                                                 last;
1910                                         }
1911                                 }
1913                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1914                                         # A Subrelease
1915                                         my @sub_releases = split(/,/, $tmp_release);
1917                                         # Walk through subreleases and build hash tree
1918                                         my $hash;
1919                                         while(my $tmp_sub_release = pop @sub_releases) {
1920                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1921                                         }
1922                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1923                                 } else {
1924                                         # A branch, no subrelease
1925                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1926                                 }
1927                         } elsif (!$entry->exists('cn')) {
1928                                 my $tmp_dn= $entry->dn();
1929                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1931                                 # Skip classes without releases
1932                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1933                                         next;
1934                                 }
1936                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1937                                         # A Subrelease
1938                                         my @sub_releases= split(/,/, $tmp_release);
1940                                         # Walk through subreleases and build hash tree
1941                                         my $hash;
1942                                         while(my $tmp_sub_release = pop @sub_releases) {
1943                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1944                                         }
1945                                         # Remove the last two characters
1946                                         chop($hash);
1947                                         chop($hash);
1949                                         eval('$fai_classes->'.$hash.'= {}');
1950                                 } else {
1951                                         # A branch, no subrelease
1952                                         if(!exists($fai_classes->{$tmp_release})) {
1953                                                 $fai_classes->{$tmp_release} = {};
1954                                         }
1955                                 }
1956                         }
1957                 }
1959                 # The hash is complete, now we can honor the copy-on-write based missing entries
1960                 foreach my $release (keys %$fai_classes) {
1961                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1962                 }
1963         }
1964         return $result;
1967 sub apply_fai_inheritance {
1968        my $fai_classes = shift || return {};
1969        my $tmp_classes;
1971        # Get the classes from the branch
1972        foreach my $class (keys %{$fai_classes}) {
1973                # Skip subreleases
1974                if($class =~ /^ou=.*$/) {
1975                        next;
1976                } else {
1977                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1978                }
1979        }
1981        # Apply to each subrelease
1982        foreach my $subrelease (keys %{$fai_classes}) {
1983                if($subrelease =~ /ou=/) {
1984                        foreach my $tmp_class (keys %{$tmp_classes}) {
1985                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1986                                        $fai_classes->{$subrelease}->{$tmp_class} =
1987                                        deep_copy($tmp_classes->{$tmp_class});
1988                                } else {
1989                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1990                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1991                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1992                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1993                                                }
1994                                        }
1995                                }
1996                        }
1997                }
1998        }
2000        # Find subreleases in deeper levels
2001        foreach my $subrelease (keys %{$fai_classes}) {
2002                if($subrelease =~ /ou=/) {
2003                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2004                                if($subsubrelease =~ /ou=/) {
2005                                        apply_fai_inheritance($fai_classes->{$subrelease});
2006                                }
2007                        }
2008                }
2009        }
2011        return $fai_classes;
2014 sub get_fai_release_entries {
2015         my $tmp_classes = shift || return;
2016         my $parent = shift || "";
2017         my @result = shift || ();
2019         foreach my $entry (keys %{$tmp_classes}) {
2020                 if(defined($entry)) {
2021                         if($entry =~ /^ou=.*$/) {
2022                                 my $release_name = $entry;
2023                                 $release_name =~ s/ou=//g;
2024                                 if(length($parent)>0) {
2025                                         $release_name = $parent."/".$release_name;
2026                                 }
2027                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2028                                 foreach my $bufentry(@bufentries) {
2029                                         push @result, $bufentry;
2030                                 }
2031                         } else {
2032                                 my @types = get_fai_types($tmp_classes->{$entry});
2033                                 foreach my $type (@types) {
2034                                         push @result, 
2035                                         {
2036                                                 'class' => $entry,
2037                                                 'type' => $type->{'type'},
2038                                                 'release' => $parent,
2039                                                 'state' => $type->{'state'},
2040                                         };
2041                                 }
2042                         }
2043                 }
2044         }
2046         return @result;
2049 sub deep_copy {
2050         my $this = shift;
2051         if (not ref $this) {
2052                 $this;
2053         } elsif (ref $this eq "ARRAY") {
2054                 [map deep_copy($_), @$this];
2055         } elsif (ref $this eq "HASH") {
2056                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2057         } else { die "what type is $_?" }
2061 sub session_run_result {
2062     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2063     $kernel->sig(CHLD => "child_reap");
2066 sub session_run_debug {
2067     my $result = $_[ARG0];
2068     print STDERR "$result\n";
2071 sub session_run_done {
2072     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2073     delete $heap->{task}->{$task_id};
2077 sub create_sources_list {
2078         my $session_id = shift;
2079         my $ldap_handle = &main::get_ldap_handle;
2080         my $result="/tmp/gosa_si_tmp_sources_list";
2082         # Remove old file
2083         if(stat($result)) {
2084                 unlink($result);
2085                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2086         }
2088         my $fh;
2089         open($fh, ">$result");
2090         if (not defined $fh) {
2091                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2092                 return undef;
2093         }
2094         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2095                 my $mesg=$ldap_handle->search(
2096                         base    => $main::ldap_server_dn,
2097                         scope   => 'base',
2098                         attrs   => 'FAIrepository',
2099                         filter  => 'objectClass=FAIrepositoryServer'
2100                 );
2101                 if($mesg->count) {
2102                         foreach my $entry(@{$mesg->{'entries'}}) {
2103                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2104                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2105                                         my $line = "deb $server $release";
2106                                         $sections =~ s/,/ /g;
2107                                         $line.= " $sections";
2108                                         print $fh $line."\n";
2109                                 }
2110                         }
2111                 }
2112         } else {
2113                 if (defined $main::ldap_server_dn){
2114                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2115                 } else {
2116                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2117                 }
2118         }
2119         close($fh);
2121         return $result;
2125 sub run_create_packages_list_db {
2126     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2127         my $session_id = $session->ID;
2129         my $task = POE::Wheel::Run->new(
2130                                         Priority => +20,
2131                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2132                                         StdoutEvent  => "session_run_result",
2133                                         StderrEvent  => "session_run_debug",
2134                                         CloseEvent   => "session_run_done",
2135                                         );
2136         $heap->{task}->{ $task->ID } = $task;
2140 sub create_packages_list_db {
2141         my ($ldap_handle, $sources_file, $session_id) = @_;
2142         
2143         # it should not be possible to trigger a recreation of packages_list_db
2144         # while packages_list_db is under construction, so set flag packages_list_under_construction
2145         # which is tested befor recreation can be started
2146         if (-r $packages_list_under_construction) {
2147                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2148                 return;
2149         } else {
2150                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2151                 # set packages_list_under_construction to true
2152                 system("touch $packages_list_under_construction");
2153                 @packages_list_statements=();
2154         }
2156         if (not defined $session_id) { $session_id = 0; }
2157         if (not defined $ldap_handle) { 
2158                 $ldap_handle= &get_ldap_handle();
2160                 if (not defined $ldap_handle) {
2161                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2162                         unlink($packages_list_under_construction);
2163                         return;
2164                 }
2165         }
2166         if (not defined $sources_file) { 
2167                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2168                 $sources_file = &create_sources_list($session_id);
2169         }
2171         if (not defined $sources_file) {
2172                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2173                 unlink($packages_list_under_construction);
2174                 return;
2175         }
2177         my $line;
2179         open(CONFIG, "<$sources_file") or do {
2180                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2181                 unlink($packages_list_under_construction);
2182                 return;
2183         };
2185         # Read lines
2186         while ($line = <CONFIG>){
2187                 # Unify
2188                 chop($line);
2189                 $line =~ s/^\s+//;
2190                 $line =~ s/^\s+/ /;
2192                 # Strip comments
2193                 $line =~ s/#.*$//g;
2195                 # Skip empty lines
2196                 if ($line =~ /^\s*$/){
2197                         next;
2198                 }
2200                 # Interpret deb line
2201                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2202                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2203                         my $section;
2204                         foreach $section (split(' ', $sections)){
2205                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2206                         }
2207                 }
2208         }
2210         close (CONFIG);
2212         find(\&cleanup_and_extract, keys( %repo_dirs ));
2213         &main::strip_packages_list_statements();
2214         unshift @packages_list_statements, "VACUUM";
2215         $packages_list_db->exec_statementlist(\@packages_list_statements);
2216         unlink($packages_list_under_construction);
2217         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2218         return;
2221 # This function should do some intensive task to minimize the db-traffic
2222 sub strip_packages_list_statements {
2223     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2224         my @new_statement_list=();
2225         my $hash;
2226         my $insert_hash;
2227         my $update_hash;
2228         my $delete_hash;
2229         my $local_timestamp=get_time();
2231         foreach my $existing_entry (@existing_entries) {
2232                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2233         }
2235         foreach my $statement (@packages_list_statements) {
2236                 if($statement =~ /^INSERT/i) {
2237                         # Assign the values from the insert statement
2238                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2239                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2240                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2241                                 # If section or description has changed, update the DB
2242                                 if( 
2243                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2244                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2245                                 ) {
2246                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2247                                 }
2248                         } else {
2249                                 # Insert a non-existing entry to db
2250                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2251                         }
2252                 } elsif ($statement =~ /^UPDATE/i) {
2253                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2254                         /^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;
2255                         foreach my $distribution (keys %{$hash}) {
2256                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2257                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2258                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2259                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2260                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2261                                                 my $section;
2262                                                 my $description;
2263                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2264                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2265                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2266                                                 }
2267                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2268                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2269                                                 }
2270                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2271                                         }
2272                                 }
2273                         }
2274                 }
2275         }
2277         # TODO: Check for orphaned entries
2279         # unroll the insert_hash
2280         foreach my $distribution (keys %{$insert_hash}) {
2281                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2282                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2283                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2284                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2285                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2286                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2287                                 ."'$local_timestamp')";
2288                         }
2289                 }
2290         }
2292         # unroll the update hash
2293         foreach my $distribution (keys %{$update_hash}) {
2294                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2295                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2296                                 my $set = "";
2297                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2298                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2299                                 }
2300                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2301                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2302                                 }
2303                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2304                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2305                                 }
2306                                 if(defined($set) and length($set) > 0) {
2307                                         $set .= "timestamp = '$local_timestamp'";
2308                                 } else {
2309                                         next;
2310                                 }
2311                                 push @new_statement_list, 
2312                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2313                                         ." distribution = '$distribution'"
2314                                         ." AND package = '$package'"
2315                                         ." AND version = '$version'";
2316                         }
2317                 }
2318         }
2320         @packages_list_statements = @new_statement_list;
2324 sub parse_package_info {
2325     my ($baseurl, $dist, $section, $session_id)= @_;
2326     my ($package);
2327     if (not defined $session_id) { $session_id = 0; }
2328     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2329     $repo_dirs{ "${repo_path}/pool" } = 1;
2331     foreach $package ("Packages.gz"){
2332         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2333         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2334         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2335     }
2336     
2340 sub get_package {
2341     my ($url, $dest, $session_id)= @_;
2342     if (not defined $session_id) { $session_id = 0; }
2344     my $tpath = dirname($dest);
2345     -d "$tpath" || mkpath "$tpath";
2347     # This is ugly, but I've no time to take a look at "how it works in perl"
2348     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2349         system("gunzip -cd '$dest' > '$dest.in'");
2350         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2351         unlink($dest);
2352         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2353     } else {
2354         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2355     }
2356     return 0;
2360 sub parse_package {
2361     my ($path, $dist, $srv_path, $session_id)= @_;
2362     if (not defined $session_id) { $session_id = 0;}
2363     my ($package, $version, $section, $description);
2364     my $PACKAGES;
2365     my $timestamp = &get_time();
2367     if(not stat("$path.in")) {
2368         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2369         return;
2370     }
2372     open($PACKAGES, "<$path.in");
2373     if(not defined($PACKAGES)) {
2374         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2375         return;
2376     }
2378     # Read lines
2379     while (<$PACKAGES>){
2380         my $line = $_;
2381         # Unify
2382         chop($line);
2384         # Use empty lines as a trigger
2385         if ($line =~ /^\s*$/){
2386             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2387             push(@packages_list_statements, $sql);
2388             $package = "none";
2389             $version = "none";
2390             $section = "none";
2391             $description = "none"; 
2392             next;
2393         }
2395         # Trigger for package name
2396         if ($line =~ /^Package:\s/){
2397             ($package)= ($line =~ /^Package: (.*)$/);
2398             next;
2399         }
2401         # Trigger for version
2402         if ($line =~ /^Version:\s/){
2403             ($version)= ($line =~ /^Version: (.*)$/);
2404             next;
2405         }
2407         # Trigger for description
2408         if ($line =~ /^Description:\s/){
2409             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2410             next;
2411         }
2413         # Trigger for section
2414         if ($line =~ /^Section:\s/){
2415             ($section)= ($line =~ /^Section: (.*)$/);
2416             next;
2417         }
2419         # Trigger for filename
2420         if ($line =~ /^Filename:\s/){
2421             my ($filename) = ($line =~ /^Filename: (.*)$/);
2422             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2423             next;
2424         }
2425     }
2427     close( $PACKAGES );
2428     unlink( "$path.in" );
2429     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2433 sub store_fileinfo {
2434     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2436     my %fileinfo = (
2437         'package' => $package,
2438         'dist' => $dist,
2439         'version' => $vers,
2440     );
2442     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2446 sub cleanup_and_extract {
2447     my $fileinfo = $repo_files{ $File::Find::name };
2449     if( defined $fileinfo ) {
2451         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2452         my $sql;
2453         my $package = $fileinfo->{ 'package' };
2454         my $newver = $fileinfo->{ 'version' };
2456         mkpath($dir);
2457         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2459                 if( -f "$dir/DEBIAN/templates" ) {
2461                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2463                         my $tmpl= "";
2464                         {
2465                                 local $/=undef;
2466                                 open FILE, "$dir/DEBIAN/templates";
2467                                 $tmpl = &encode_base64(<FILE>);
2468                                 close FILE;
2469                         }
2470                         rmtree("$dir/DEBIAN/templates");
2472                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2473                 push @packages_list_statements, $sql;
2474                 }
2475     }
2477     return;
2481 #==== MAIN = main ==============================================================
2482 #  parse commandline options
2483 Getopt::Long::Configure( "bundling" );
2484 GetOptions("h|help" => \&usage,
2485         "c|config=s" => \$cfg_file,
2486         "f|foreground" => \$foreground,
2487         "v|verbose+" => \$verbose,
2488         "no-bus+" => \$no_bus,
2489         "no-arp+" => \$no_arp,
2490            );
2492 #  read and set config parameters
2493 &check_cmdline_param ;
2494 &read_configfile;
2495 &check_pid;
2497 $SIG{CHLD} = 'IGNORE';
2499 # forward error messages to logfile
2500 if( ! $foreground ) {
2501   open( STDIN,  '+>/dev/null' );
2502   open( STDOUT, '+>&STDIN'    );
2503   open( STDERR, '+>&STDIN'    );
2506 # Just fork, if we are not in foreground mode
2507 if( ! $foreground ) { 
2508     chdir '/'                 or die "Can't chdir to /: $!";
2509     $pid = fork;
2510     setsid                    or die "Can't start a new session: $!";
2511     umask 0;
2512 } else { 
2513     $pid = $$; 
2516 # Do something useful - put our PID into the pid_file
2517 if( 0 != $pid ) {
2518     open( LOCK_FILE, ">$pid_file" );
2519     print LOCK_FILE "$pid\n";
2520     close( LOCK_FILE );
2521     if( !$foreground ) { 
2522         exit( 0 ) 
2523     };
2526 # parse head url and revision from svn
2527 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2528 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2529 $server_headURL = defined $1 ? $1 : 'unknown' ;
2530 $server_revision = defined $2 ? $2 : 'unknown' ;
2531 if ($server_headURL =~ /\/tag\// || 
2532         $server_headURL =~ /\/branches\// ) {
2533     $server_status = "stable"; 
2534 } else {
2535     $server_status = "developmental" ;
2539 daemon_log(" ", 1);
2540 daemon_log("$0 started!", 1);
2541 daemon_log("status: $server_status", 1);
2542 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2544 if ($no_bus > 0) {
2545     $bus_activ = "false"
2548 # connect to gosa-si job queue
2549 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2550 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2552 # connect to known_clients_db
2553 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2554 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2556 # connect to known_server_db
2557 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2558 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2560 # connect to login_usr_db
2561 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2562 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2564 # connect to fai_server_db and fai_release_db
2565 unlink($fai_server_file_name);
2566 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2567 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2569 unlink($fai_release_file_name);
2570 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2571 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2573 # connect to packages_list_db
2574 #unlink($packages_list_file_name);
2575 unlink($packages_list_under_construction);
2576 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2577 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2579 # connect to messaging_db
2580 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2581 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2584 # create xml object used for en/decrypting
2585 $xml = new XML::Simple();
2587 # create socket for incoming xml messages
2589 POE::Component::Server::TCP->new(
2590         Port => $server_port,
2591         ClientInput => sub {
2592         my ($kernel, $input) = @_[KERNEL, ARG0];
2593         push(@tasks, $input);
2594         $kernel->yield("next_task");
2595         },
2596     InlineStates => {
2597         next_task => \&next_task,
2598         task_result => \&handle_task_result,
2599         task_done   => \&handle_task_done,
2600         task_debug  => \&handle_task_debug,
2601         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2602     }
2603 );
2605 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2607 # create session for repeatedly checking the job queue for jobs
2608 POE::Session->create(
2609         inline_states => {
2610                 _start => \&_start,
2611                 sig_handler => \&sig_handler,
2612         watch_for_new_messages => \&watch_for_new_messages,
2613         watch_for_delivery_messages => \&watch_for_delivery_messages,
2614         watch_for_done_messages => \&watch_for_done_messages,
2615                 watch_for_new_jobs => \&watch_for_new_jobs,
2616         watch_for_done_jobs => \&watch_for_done_jobs,
2617         create_packages_list_db => \&run_create_packages_list_db,
2618         create_fai_server_db => \&run_create_fai_server_db,
2619         create_fai_release_db => \&run_create_fai_release_db,
2620         session_run_result => \&session_run_result,
2621         session_run_debug => \&session_run_debug,
2622         session_run_done => \&session_run_done,
2623         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2624         }
2625 );
2628 # import all modules
2629 &import_modules;
2631 # check wether all modules are gosa-si valid passwd check
2633 POE::Kernel->run();
2634 exit;