Code

9357685bd0a2bebc64d6597a9d6b2894d8bc0a82
[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 #===============================================================================
23 # TODO es gibt eine globale funktion get_ldap_handle
24 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
25 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
26 # - sessions die kein ldap handle brauchen, sollen auch keins haben
27 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
30 use strict;
31 use warnings;
32 use Getopt::Long;
33 use Config::IniFiles;
34 use POSIX;
36 use Fcntl;
37 use IO::Socket::INET;
38 use IO::Handle;
39 use IO::Select;
40 use Symbol qw(qualify_to_ref);
41 use Crypt::Rijndael;
42 use MIME::Base64;
43 use Digest::MD5  qw(md5 md5_hex md5_base64);
44 use XML::Simple;
45 use Data::Dumper;
46 use Sys::Syslog qw( :DEFAULT setlogsock);
47 use Cwd;
48 use File::Spec;
49 use File::Basename;
50 use File::Find;
51 use File::Copy;
52 use File::Path;
53 use GOSA::DBsqlite;
54 use GOSA::GosaSupportDaemon;
55 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
56 use Net::LDAP;
57 use Net::LDAP::Util qw(:escape);
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
63 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
64 my ($server);
65 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
66 my ($known_modules);
67 my ($pid_file, $procid, $pid, $log_file);
68 my ($arp_activ, $arp_fifo);
69 my ($xml);
70 my $sources_list;
71 my $max_clients;
72 my %repo_files=();
73 my $repo_path;
74 my %repo_dirs=();
75 # variables declared in config file are always set to 'our'
76 our (%cfg_defaults, $log_file, $pid_file, 
77     $server_ip, $server_port, $SIPackages_key, 
78     $arp_activ, $gosa_unit_tag,
79     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
80 );
82 # additional variable which should be globaly accessable
83 our $server_address;
84 our $server_mac_address;
85 our $bus_address;
86 our $gosa_address;
87 our $no_bus;
88 our $no_arp;
89 our $verbose;
90 our $forground;
91 our $cfg_file;
92 our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
95 # specifies the verbosity of the daemon_log
96 $verbose = 0 ;
98 # if foreground is not null, script will be not forked to background
99 $foreground = 0 ;
101 # specifies the timeout seconds while checking the online status of a registrating client
102 $ping_timeout = 5;
104 $no_bus = 0;
105 $bus_activ = "true";
107 $no_arp = 0;
109 our $prg= basename($0);
111 # holds all gosa jobs
112 our $job_db;
113 our $job_queue_tn = 'jobs';
114 my $job_queue_file_name;
115 my @job_queue_col_names = ("id INTEGER", 
116                 "timestamp", 
117                 "status DEFAULT 'none'", 
118                 "result DEFAULT 'none'", 
119                 "progress DEFAULT 'none'", 
120                 "headertag DEFAULT 'none'", 
121                 "targettag DEFAULT 'none'", 
122                 "xmlmessage DEFAULT 'none'", 
123                 "macaddress DEFAULT 'none'",
124                 );
126 # holds all other gosa-sd as well as the gosa-sd-bus
127 our $known_server_db;
128 our $known_server_tn = "known_server";
129 my $known_server_file_name;
130 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
132 # holds all registrated clients
133 our $known_clients_db;
134 our $known_clients_tn = "known_clients";
135 my $known_clients_file_name;
136 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
138 # holds all logged in user at each client 
139 our $login_users_db;
140 our $login_users_tn = "login_users";
141 my $login_users_file_name;
142 my @login_users_col_names = ('client', 'user', 'timestamp');
144 # holds all fai server, the debian release and tag
145 our $fai_server_db;
146 our $fai_server_tn = "fai_server"; 
147 my $fai_server_file_name;
148 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag'); 
149 our $fai_release_tn = "fai_release"; 
150 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state'); 
152 # holds all packages available from different repositories
153 our $packages_list_db;
154 our $packages_list_tn = "packages_list";
155 my $packages_list_file_name;
156 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
157 my $outdir = "/tmp/packages_list_db";
158 my $arch = "i386"; 
160 # holds all messages which should be delivered to a user
161 our $messaging_db;
162 our $messaging_tn = "messaging"; 
163 our @messaging_col_names = ('subject', 'from', 'to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER', );
164 my $messaging_file_name;
166 # path to directory to store client install log files
167 our $client_fai_log_dir = "/var/log/fai"; 
169 # queue which stores taskes until one of the $max_children children are ready to process the task
170 my @tasks = qw();
171 my $max_children = 2;
174 %cfg_defaults = (
175 "general" => {
176     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
177     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
178     },
179 "bus" => {
180     "activ" => [\$bus_activ, "true"],
181     },
182 "server" => {
183     "port" => [\$server_port, "20081"],
184     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
185     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
186     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
187     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
188     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
189     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
190     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
191     "repo-path" => [\$repo_path, '/srv/www/repository'],
192     "ldap-uri" => [\$ldap_uri, ""],
193     "ldap-base" => [\$ldap_base, ""],
194     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
195     "ldap-admin-password" => [\$ldap_admin_password, ""],
196     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
197     "max-clients" => [\$max_clients, 10],
198     },
199 "GOsaPackages" => {
200     "ip" => [\$gosa_ip, "0.0.0.0"],
201     "port" => [\$gosa_port, "20082"],
202     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
203     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
204     "key" => [\$GosaPackages_key, "none"],
205     },
206 "SIPackages" => {
207     "key" => [\$SIPackages_key, "none"],
208     },
209 );
212 #===  FUNCTION  ================================================================
213 #         NAME:  usage
214 #   PARAMETERS:  nothing
215 #      RETURNS:  nothing
216 #  DESCRIPTION:  print out usage text to STDERR
217 #===============================================================================
218 sub usage {
219     print STDERR << "EOF" ;
220 usage: $prg [-hvf] [-c config]
222            -h        : this (help) message
223            -c <file> : config file
224            -f        : foreground, process will not be forked to background
225            -v        : be verbose (multiple to increase verbosity)
226            -no-bus   : starts $prg without connection to bus
227            -no-arp   : starts $prg without connection to arp module
228  
229 EOF
230     print "\n" ;
234 #===  FUNCTION  ================================================================
235 #         NAME:  read_configfile
236 #   PARAMETERS:  cfg_file - string -
237 #      RETURNS:  nothing
238 #  DESCRIPTION:  read cfg_file and set variables
239 #===============================================================================
240 sub read_configfile {
241     my $cfg;
242     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
243         if( -r $cfg_file ) {
244             $cfg = Config::IniFiles->new( -file => $cfg_file );
245         } else {
246             print STDERR "Couldn't read config file!\n";
247         }
248     } else {
249         $cfg = Config::IniFiles->new() ;
250     }
251     foreach my $section (keys %cfg_defaults) {
252         foreach my $param (keys %{$cfg_defaults{ $section }}) {
253             my $pinfo = $cfg_defaults{ $section }{ $param };
254             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
255         }
256     }
260 #===  FUNCTION  ================================================================
261 #         NAME:  logging
262 #   PARAMETERS:  level - string - default 'info'
263 #                msg - string -
264 #                facility - string - default 'LOG_DAEMON'
265 #      RETURNS:  nothing
266 #  DESCRIPTION:  function for logging
267 #===============================================================================
268 sub daemon_log {
269     # log into log_file
270     my( $msg, $level ) = @_;
271     if(not defined $msg) { return }
272     if(not defined $level) { $level = 1 }
273     if(defined $log_file){
274         open(LOG_HANDLE, ">>$log_file");
275         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
276             print STDERR "cannot open $log_file: $!";
277             return }
278             chomp($msg);
279             if($level <= $verbose){
280                 my ($seconds, $minutes, $hours, $monthday, $month,
281                         $year, $weekday, $yearday, $sommertime) = localtime(time);
282                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
283                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
284                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
285                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
286                 $month = $monthnames[$month];
287                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
288                 $year+=1900;
289                 my $name = $prg;
291                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
292                 print LOG_HANDLE $log_msg;
293                 if( $foreground ) { 
294                     print STDERR $log_msg;
295                 }
296             }
297         close( LOG_HANDLE );
298     }
302 #===  FUNCTION  ================================================================
303 #         NAME:  check_cmdline_param
304 #   PARAMETERS:  nothing
305 #      RETURNS:  nothing
306 #  DESCRIPTION:  validates commandline parameter
307 #===============================================================================
308 sub check_cmdline_param () {
309     my $err_config;
310     my $err_counter = 0;
311         if(not defined($cfg_file)) {
312                 $cfg_file = "/etc/gosa-si/server.conf";
313                 if(! -r $cfg_file) {
314                         $err_config = "please specify a config file";
315                         $err_counter += 1;
316                 }
317     }
318     if( $err_counter > 0 ) {
319         &usage( "", 1 );
320         if( defined( $err_config)) { print STDERR "$err_config\n"}
321         print STDERR "\n";
322         exit( -1 );
323     }
327 #===  FUNCTION  ================================================================
328 #         NAME:  check_pid
329 #   PARAMETERS:  nothing
330 #      RETURNS:  nothing
331 #  DESCRIPTION:  handels pid processing
332 #===============================================================================
333 sub check_pid {
334     $pid = -1;
335     # Check, if we are already running
336     if( open(LOCK_FILE, "<$pid_file") ) {
337         $pid = <LOCK_FILE>;
338         if( defined $pid ) {
339             chomp( $pid );
340             if( -f "/proc/$pid/stat" ) {
341                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
342                 if( $stat ) {
343                                         daemon_log("ERROR: Already running",1);
344                     close( LOCK_FILE );
345                     exit -1;
346                 }
347             }
348         }
349         close( LOCK_FILE );
350         unlink( $pid_file );
351     }
353     # create a syslog msg if it is not to possible to open PID file
354     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
355         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
356         if (open(LOCK_FILE, '<', $pid_file)
357                 && ($pid = <LOCK_FILE>))
358         {
359             chomp($pid);
360             $msg .= "(PID $pid)\n";
361         } else {
362             $msg .= "(unable to read PID)\n";
363         }
364         if( ! ($foreground) ) {
365             openlog( $0, "cons,pid", "daemon" );
366             syslog( "warning", $msg );
367             closelog();
368         }
369         else {
370             print( STDERR " $msg " );
371         }
372         exit( -1 );
373     }
376 #===  FUNCTION  ================================================================
377 #         NAME:  import_modules
378 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
379 #                are stored
380 #      RETURNS:  nothing
381 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
382 #                state is on is imported by "require 'file';"
383 #===============================================================================
384 sub import_modules {
385     daemon_log(" ", 1);
387     if (not -e $modules_path) {
388         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
389     }
391     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
392     while (defined (my $file = readdir (DIR))) {
393         if (not $file =~ /(\S*?).pm$/) {
394             next;
395         }
396                 my $mod_name = $1;
398         if( $file =~ /ArpHandler.pm/ ) {
399             if( $no_arp > 0 ) {
400                 next;
401             }
402         }
403         
404         eval { require $file; };
405         if ($@) {
406             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
407             daemon_log("$@", 5);
408                 } else {
409                         my $info = eval($mod_name.'::get_module_info()');
410                         # Only load module if get_module_info() returns a non-null object
411                         if( $info ) {
412                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
413                                 $known_modules->{$mod_name} = $info;
414                                 daemon_log("INFO: module $mod_name loaded", 5);
415                         }
416                 }
417     }   
418     close (DIR);
422 #===  FUNCTION  ================================================================
423 #         NAME:  sig_int_handler
424 #   PARAMETERS:  signal - string - signal arose from system
425 #      RETURNS:  noting
426 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
427 #===============================================================================
428 sub sig_int_handler {
429     my ($signal) = @_;
431         if(defined($ldap_handle)) {
432                 $ldap_handle->disconnect;
433         }
435     daemon_log("shutting down gosa-si-server", 1);
436     system("killall gosa-si-server");
438 $SIG{INT} = \&sig_int_handler;
441 sub check_key_and_xml_validity {
442     my ($crypted_msg, $module_key, $session_id) = @_;
443     my $msg;
444     my $msg_hash;
445     my $error_string;
446     eval{
447         $msg = &decrypt_msg($crypted_msg, $module_key);
449         if ($msg =~ /<xml>/i){
450             $msg =~ s/\s+/ /g;  # just for better daemon_log
451             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
452             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
454             ##############
455             # check header
456             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
457             my $header_l = $msg_hash->{'header'};
458             if( 1 > @{$header_l} ) { die 'empty header tag'; }
459             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
460             my $header = @{$header_l}[0];
461             if( 0 == length $header) { die 'empty string in header tag'; }
463             ##############
464             # check source
465             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
466             my $source_l = $msg_hash->{'source'};
467             if( 1 > @{$source_l} ) { die 'empty source tag'; }
468             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
469             my $source = @{$source_l}[0];
470             if( 0 == length $source) { die 'source error'; }
472             ##############
473             # check target
474             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
475             my $target_l = $msg_hash->{'target'};
476             if( 1 > @{$target_l} ) { die 'empty target tag'; }
477         }
478     };
479     if($@) {
480         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
481         $msg = undef;
482         $msg_hash = undef;
483     }
485     return ($msg, $msg_hash);
489 sub check_outgoing_xml_validity {
490     my ($msg) = @_;
492     my $msg_hash;
493     eval{
494         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
496         ##############
497         # check header
498         my $header_l = $msg_hash->{'header'};
499         if( 1 != @{$header_l} ) {
500             die 'no or more than one headers specified';
501         }
502         my $header = @{$header_l}[0];
503         if( 0 == length $header) {
504             die 'header has length 0';
505         }
507         ##############
508         # check source
509         my $source_l = $msg_hash->{'source'};
510         if( 1 != @{$source_l} ) {
511             die 'no or more than 1 sources specified';
512         }
513         my $source = @{$source_l}[0];
514         if( 0 == length $source) {
515             die 'source has length 0';
516         }
517         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
518                 $source =~ /^GOSA$/i ) {
519             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
520         }
521         
522         ##############
523         # check target  
524         my $target_l = $msg_hash->{'target'};
525         if( 0 == @{$target_l} ) {
526             die "no targets specified";
527         }
528         foreach my $target (@$target_l) {
529             if( 0 == length $target) {
530                 die "target has length 0";
531             }
532             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
533                     $target =~ /^GOSA$/i ||
534                     $target =~ /^\*$/ ||
535                     $target =~ /KNOWN_SERVER/i ||
536                     $target =~ /JOBDB/i ||
537                     $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 ){
538                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
539             }
540         }
541     };
542     if($@) {
543         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
544         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
545         $msg_hash = undef;
546     }
548     return ($msg_hash);
552 sub input_from_known_server {
553     my ($input, $remote_ip, $session_id) = @_ ;  
554     my ($msg, $msg_hash, $module);
556     my $sql_statement= "SELECT * FROM known_server";
557     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
559     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
560         my $host_name = $hit->{hostname};
561         if( not $host_name =~ "^$remote_ip") {
562             next;
563         }
564         my $host_key = $hit->{hostkey};
565         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
566         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
568         # check if module can open msg envelope with module key
569         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
570         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
571             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
572             daemon_log("$@", 8);
573             next;
574         }
575         else {
576             $msg = $tmp_msg;
577             $msg_hash = $tmp_msg_hash;
578             $module = "SIPackages";
579             last;
580         }
581     }
583     if( (!$msg) || (!$msg_hash) || (!$module) ) {
584         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
585     }
586   
587     return ($msg, $msg_hash, $module);
591 sub input_from_known_client {
592     my ($input, $remote_ip, $session_id) = @_ ;  
593     my ($msg, $msg_hash, $module);
595     my $sql_statement= "SELECT * FROM known_clients";
596     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
597     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
598         my $host_name = $hit->{hostname};
599         if( not $host_name =~ /^$remote_ip:\d*$/) {
600                 next;
601                 }
602         my $host_key = $hit->{hostkey};
603         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
604         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
606         # check if module can open msg envelope with module key
607         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
609         if( (!$msg) || (!$msg_hash) ) {
610             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
611             &daemon_log("$@", 8);
612             next;
613         }
614         else {
615             $module = "SIPackages";
616             last;
617         }
618     }
620     if( (!$msg) || (!$msg_hash) || (!$module) ) {
621         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
622     }
624     return ($msg, $msg_hash, $module);
628 sub input_from_unknown_host {
629     no strict "refs";
630     my ($input, $session_id) = @_ ;
631     my ($msg, $msg_hash, $module);
632     my $error_string;
633     
634         my %act_modules = %$known_modules;
636         while( my ($mod, $info) = each(%act_modules)) {
638         # check a key exists for this module
639         my $module_key = ${$mod."_key"};
640         if( not defined $module_key ) {
641             if( $mod eq 'ArpHandler' ) {
642                 next;
643             }
644             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
645             next;
646         }
647         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
649         # check if module can open msg envelope with module key
650         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
651         if( (not defined $msg) || (not defined $msg_hash) ) {
652             next;
653         }
654         else {
655             $module = $mod;
656             last;
657         }
658     }
660     if( (!$msg) || (!$msg_hash) || (!$module)) {
661         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
662     }
664     return ($msg, $msg_hash, $module);
668 sub create_ciphering {
669     my ($passwd) = @_;
670         if((!defined($passwd)) || length($passwd)==0) {
671                 $passwd = "";
672         }
673     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
674     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
675     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
676     $my_cipher->set_iv($iv);
677     return $my_cipher;
681 sub encrypt_msg {
682     my ($msg, $key) = @_;
683     my $my_cipher = &create_ciphering($key);
684     my $len;
685     {
686             use bytes;
687             $len= 16-length($msg)%16;
688     }
689     $msg = "\0"x($len).$msg;
690     $msg = $my_cipher->encrypt($msg);
691     chomp($msg = &encode_base64($msg));
692     # there are no newlines allowed inside msg
693     $msg=~ s/\n//g;
694     return $msg;
698 sub decrypt_msg {
700     my ($msg, $key) = @_ ;
701     $msg = &decode_base64($msg);
702     my $my_cipher = &create_ciphering($key);
703     $msg = $my_cipher->decrypt($msg); 
704     $msg =~ s/\0*//g;
705     return $msg;
709 sub get_encrypt_key {
710     my ($target) = @_ ;
711     my $encrypt_key;
712     my $error = 0;
714     # target can be in known_server
715     if( not defined $encrypt_key ) {
716         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
717         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
718         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
719             my $host_name = $hit->{hostname};
720             if( $host_name ne $target ) {
721                 next;
722             }
723             $encrypt_key = $hit->{hostkey};
724             last;
725         }
726     }
728     # target can be in known_client
729     if( not defined $encrypt_key ) {
730         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
731         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
732         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
733             my $host_name = $hit->{hostname};
734             if( $host_name ne $target ) {
735                 next;
736             }
737             $encrypt_key = $hit->{hostkey};
738             last;
739         }
740     }
742     return $encrypt_key;
746 #===  FUNCTION  ================================================================
747 #         NAME:  open_socket
748 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
749 #                [PeerPort] string necessary if port not appended by PeerAddr
750 #      RETURNS:  socket IO::Socket::INET
751 #  DESCRIPTION:  open a socket to PeerAddr
752 #===============================================================================
753 sub open_socket {
754     my ($PeerAddr, $PeerPort) = @_ ;
755     if(defined($PeerPort)){
756         $PeerAddr = $PeerAddr.":".$PeerPort;
757     }
758     my $socket;
759     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
760             Porto => "tcp",
761             Type => SOCK_STREAM,
762             Timeout => 5,
763             );
764     if(not defined $socket) {
765         return;
766     }
767 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
768     return $socket;
772 #===  FUNCTION  ================================================================
773 #         NAME:  get_ip 
774 #   PARAMETERS:  interface name (i.e. eth0)
775 #      RETURNS:  (ip address) 
776 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
777 #===============================================================================
778 sub get_ip {
779         my $ifreq= shift;
780         my $result= "";
781         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
782         my $proto= getprotobyname('ip');
784         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
785                 or die "socket: $!";
787         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
788                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
789                 my ($port, $addr) = sockaddr_in $sin;
790                 my $ip            = inet_ntoa $addr;
792                 if ($ip && length($ip) > 0) {
793                         $result = $ip;
794                 }
795         }
797         return $result;
801 sub get_local_ip_for_remote_ip {
802         my $remote_ip= shift;
803         my $result="0.0.0.0";
805         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
806                 if($remote_ip eq "127.0.0.1") {
807                         $result = "127.0.0.1";
808                 } else {
809                         my $PROC_NET_ROUTE= ('/proc/net/route');
811                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
812                                 or die "Could not open $PROC_NET_ROUTE";
814                         my @ifs = <PROC_NET_ROUTE>;
816                         close(PROC_NET_ROUTE);
818                         # Eat header line
819                         shift @ifs;
820                         chomp @ifs;
821                         foreach my $line(@ifs) {
822                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
823                                 my $destination;
824                                 my $mask;
825                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
826                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
827                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
828                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
829                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
830                                         # destination matches route, save mac and exit
831                                         $result= &get_ip($Iface);
832                                         last;
833                                 }
834                         }
835                 }
836         } else {
837                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
838         }
839         return $result;
843 sub send_msg_to_target {
844     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
845     my $error = 0;
846     my $header;
847     my $new_status;
848     my $act_status;
849     my ($sql_statement, $res);
850   
851     if( $msg_header ) {
852         $header = "'$msg_header'-";
853     } else {
854         $header = "";
855     }
857         # Patch the source ip
858         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
859                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
860                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
861         }
863     # encrypt xml msg
864     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
866     # opensocket
867     my $socket = &open_socket($address);
868     if( !$socket ) {
869         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
870         $error++;
871     }
872     
873     if( $error == 0 ) {
874         # send xml msg
875         print $socket $crypted_msg."\n";
877         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
878         #daemon_log("DEBUG: message:\n$msg", 9);
879         
880     }
882     # close socket in any case
883     if( $socket ) {
884         close $socket;
885     }
887     if( $error > 0 ) { $new_status = "down"; }
888     else { $new_status = $msg_header; }
891     # known_clients
892     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
893     $res = $known_clients_db->select_dbentry($sql_statement);
894     if( keys(%$res) > 0) {
895         $act_status = $res->{1}->{'status'};
896         if( $act_status eq "down" ) {
897             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
898             $res = $known_clients_db->del_dbentry($sql_statement);
899             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
900         } else { 
901             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
902             $res = $known_clients_db->update_dbentry($sql_statement);
903             if($new_status eq "down"){
904                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
905             } else {
906                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
907             }
908         }
909     }
911     # known_server
912     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
913     $res = $known_server_db->select_dbentry($sql_statement);
914     if( keys(%$res) > 0 ) {
915         $act_status = $res->{1}->{'status'};
916         if( $act_status eq "down" ) {
917             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
918             $res = $known_server_db->del_dbentry($sql_statement);
919             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
920         } 
921         else { 
922             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
923             $res = $known_server_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             }
927             else {
928                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
929             }
930         }
931     }
932     return $error; 
936 sub update_jobdb_status_for_send_msgs {
937     my ($answer, $error) = @_;
938     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
939         my $jobdb_id = $1;
940             
941         # sending msg faild
942         if( $error ) {
943             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
944                 my $sql_statement = "UPDATE $job_queue_tn ".
945                     "SET status='error', result='can not deliver msg, please consult log file' ".
946                     "WHERE id='$jobdb_id'";
947                 my $res = $job_db->update_dbentry($sql_statement);
948             }
950         # sending msg was successful
951         } else {
952             my $sql_statement = "UPDATE $job_queue_tn ".
953                 "SET status='done' ".
954                 "WHERE id='$jobdb_id' AND status='processed'";
955             my $res = $job_db->update_dbentry($sql_statement);
956         }
957     }
960 sub _start {
961     my ($kernel) = $_[KERNEL];
962     &trigger_db_loop($kernel);
963         $kernel->yield('create_fai_server_db', $fai_server_tn );
964         $kernel->yield('create_fai_release_db', $fai_release_tn );
965         $kernel->sig(USR1 => "sig_handler");
968 sub sig_handler {
969         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
970         daemon_log("0 INFO got signal '$signal'", 1); 
971         $kernel->sig_handled();
972         return;
975 sub next_task {
976     my ($session, $heap) = @_[SESSION, HEAP];
978     while ( keys( %{ $heap->{task} } ) < $max_children ) {
979         my $next_task = shift @tasks;
980         last unless defined $next_task;
982         my $task = POE::Wheel::Run->new(
983                 Program => sub { process_task($session, $heap, $next_task) },
984                 StdioFilter => POE::Filter::Reference->new(),
985                 StdoutEvent  => "task_result",
986                 StderrEvent  => "task_debug",
987                 CloseEvent   => "task_done",
988                );
990         $heap->{task}->{ $task->ID } = $task;
991     }
994 sub handle_task_result {
995     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
996     my $client_answer = $result->{'answer'};
997     if( $client_answer =~ s/session_id=(\d+)$// ) {
998         my $session_id = $1;
999         if( defined $session_id ) {
1000             my $session_reference = $kernel->ID_id_to_session($session_id);
1001             if( defined $session_reference ) {
1002                 $heap = $session_reference->get_heap();
1003             }
1004         }
1006         if(exists $heap->{'client'}) {
1007             $heap->{'client'}->put($client_answer);
1008         }
1009     }
1010     $kernel->sig(CHLD => "child_reap");
1013 sub handle_task_debug {
1014     my $result = $_[ARG0];
1015     print STDERR "$result\n";
1018 sub handle_task_done {
1019     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1020     delete $heap->{task}->{$task_id};
1021     $kernel->yield("next_task");
1024 sub process_task {
1025     no strict "refs";
1026     my ($session, $heap, $input) = @_;
1027     my $session_id = $session->ID;
1028     my ($msg, $msg_hash, $module);
1029     my $error = 0;
1030     my $answer_l;
1031     my ($answer_header, @answer_target_l, $answer_source);
1032     my $client_answer = "";
1034     daemon_log("", 5); 
1035     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1036     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1038     ####################
1039     # check incoming msg
1040     # msg is from a new client or gosa
1041     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1042     # msg is from a gosa-si-server or gosa-si-bus
1043     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1044         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1045     }
1046     # msg is from a gosa-si-client
1047     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1048         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1049     }
1050     # an error occurred
1051     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1052         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1053         # could not understand a msg from its server the client cause a re-registering process
1054         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1055         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1056         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1057             my $host_name = $hit->{'hostname'};
1058             my $host_key = $hit->{'hostkey'};
1059             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1060             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1061             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1062         }
1063         $error++;
1064     }
1066     ######################
1067     # process incoming msg
1068     if( $error == 0) {
1069         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1070                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1071         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1072         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1074         if ( 0 < @{$answer_l} ) {
1075             my $answer_str = join("\n", @{$answer_l});
1076             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1077         }
1078     }
1079     if( !$answer_l ) { $error++ };
1081     ########
1082     # answer
1083     if( $error == 0 ) {
1085         foreach my $answer ( @{$answer_l} ) {
1086             # for each answer in answer list
1087             
1088             # check outgoing msg to xml validity
1089             my $answer_hash = &check_outgoing_xml_validity($answer);
1090             if( not defined $answer_hash ) {
1091                 next;
1092             }
1093             
1094             $answer_header = @{$answer_hash->{'header'}}[0];
1095             @answer_target_l = @{$answer_hash->{'target'}};
1096             $answer_source = @{$answer_hash->{'source'}}[0];
1098             # deliver msg to all targets 
1099             foreach my $answer_target ( @answer_target_l ) {
1101                 # targets of msg are all gosa-si-clients in known_clients_db
1102                 if( $answer_target eq "*" ) {
1103                     # answer is for all clients
1104                     my $sql_statement= "SELECT * FROM known_clients";
1105                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1106                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1107                         my $host_name = $hit->{hostname};
1108                         my $host_key = $hit->{hostkey};
1109                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1110                         &update_jobdb_status_for_send_msgs($answer, $error);
1111                     }
1112                 }
1114                 # targets of msg are all gosa-si-server in known_server_db
1115                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1116                     # answer is for all server in known_server
1117                     my $sql_statement= "SELECT * FROM known_server";
1118                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1119                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1120                         my $host_name = $hit->{hostname};
1121                         my $host_key = $hit->{hostkey};
1122                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1123                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1124                         &update_jobdb_status_for_send_msgs($answer, $error);
1125                     }
1126                 }
1128                 # target of msg is GOsa
1129                                 elsif( $answer_target eq "GOSA" ) {
1130                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1131                                         my $add_on = "";
1132                     if( defined $session_id ) {
1133                         $add_on = ".session_id=$session_id";
1134                     }
1135                     # answer is for GOSA and has to returned to connected client
1136                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1137                     $client_answer = $gosa_answer.$add_on;
1138                 }
1140                 # target of msg is job queue at this host
1141                 elsif( $answer_target eq "JOBDB") {
1142                     $answer =~ /<header>(\S+)<\/header>/;   
1143                     my $header;
1144                     if( defined $1 ) { $header = $1; }
1145                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1146                     &update_jobdb_status_for_send_msgs($answer, $error);
1147                 }
1149                 # target of msg is a mac address
1150                 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 ) {
1151                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1152                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1153                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1154                     my $found_ip_flag = 0;
1155                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1156                         my $host_name = $hit->{hostname};
1157                         my $host_key = $hit->{hostkey};
1158                         $answer =~ s/$answer_target/$host_name/g;
1159                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1160                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1161                         &update_jobdb_status_for_send_msgs($answer, $error);
1162                         $found_ip_flag++ ;
1163                     }   
1164                     if( $found_ip_flag == 0) {
1165                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1166                         if( $bus_activ eq "true" ) { 
1167                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1168                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1169                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1170                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1171                                 my $bus_address = $hit->{hostname};
1172                                 my $bus_key = $hit->{hostkey};
1173                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1174                                 &update_jobdb_status_for_send_msgs($answer, $error);
1175                                 last;
1176                             }
1177                         }
1179                     }
1181                 #  answer is for one specific host   
1182                 } else {
1183                     # get encrypt_key
1184                     my $encrypt_key = &get_encrypt_key($answer_target);
1185                     if( not defined $encrypt_key ) {
1186                         # unknown target, forward msg to bus
1187                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1188                         if( $bus_activ eq "true" ) { 
1189                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1190                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1191                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1192                             my $res_length = keys( %{$query_res} );
1193                             if( $res_length == 0 ){
1194                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1195                                         "no bus found in known_server", 3);
1196                             }
1197                             else {
1198                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
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                                 }
1203                             }
1204                         }
1205                         next;
1206                     }
1207                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1208                     &update_jobdb_status_for_send_msgs($answer, $error);
1209                 }
1210             }
1211         }
1212     }
1214     my $filter = POE::Filter::Reference->new();
1215     my %result = ( 
1216             status => "seems ok to me",
1217             answer => $client_answer,
1218             );
1220     my $output = $filter->put( [ \%result ] );
1221     print @$output;
1227 sub trigger_db_loop {
1228         my ($kernel) = @_ ;
1229         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1230     $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1233 sub watch_for_done_jobs {
1234     my ($kernel,$heap) = @_[KERNEL, HEAP];
1236     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1237         " WHERE status='done'";
1238         my $res = $job_db->select_dbentry( $sql_statement );
1240     while( my ($id, $hit) = each %{$res} ) {
1241         my $jobdb_id = $hit->{id};
1242         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1243         my $res = $job_db->del_dbentry($sql_statement);
1244     }
1246     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1249 sub watch_for_new_jobs {
1250         my ($kernel,$heap) = @_[KERNEL, HEAP];
1252         # check gosa job queue for jobs with executable timestamp
1253     my $timestamp = &get_time();
1254     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1255         " WHERE status='waiting' AND timestamp<'$timestamp'";
1256         my $res = $job_db->select_dbentry( $sql_statement );
1258         while( my ($id, $hit) = each %{$res} ) {         
1259                 my $jobdb_id = $hit->{id};
1260                 my $macaddress = $hit->{'macaddress'};
1261         my $job_msg = $hit->{'xmlmessage'};
1262         daemon_log("J DEBUG: its time to execute $job_msg", 7); 
1263         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1264                 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1265                 # expect macaddress is unique!!!!!!
1266                 my $target = $res_hash->{1}->{hostname};
1268                 # change header
1269         $job_msg =~ s/<header>job_/<header>gosa_/;
1271                 # add sqlite_id 
1272         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1274         $job_msg =~ /<header>(\S+)<\/header>/;
1275         my $header = $1 ;
1276                 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1278         # update status in job queue to 'processing'
1279         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1280         my $res = $job_db->update_dbentry($sql_statement);
1281     }
1283         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1287 sub get_ldap_handle {
1288         my ($session_id) = @_;
1289         my $heap;
1290         my $ldap_handle;
1291         
1292         if (not defined $session_id) {
1293                 return $ldap_handle;
1294         }
1296         my $session_reference = $kernel->ID_id_to_session($session_id);
1297         if( defined $session_reference ) {
1298                 $heap = $session_reference->get_heap();
1299         }
1301         if (not exists $heap->{ldap_handle}) {
1302                 # create new ldap handle
1303                 # add ldap handle to heap
1304         }
1306         $ldap_handle = $heap->{ldap_handle};
1308         return \$ldap_handle;
1312 sub refresh_ldap_handle {
1313         my ($session_id) = @_ ;
1314         if (not defined $session_id) { $session_id = 0; } 
1315         
1316   my $mesg;
1318   daemon_log("$session_id DEBUG: Trying to create a connection to URI '$ldap_uri'", 7);
1319   # Get an ldap handle, if we don't have one
1320   if( ! defined $ldap_handle ){
1321           $ldap_handle = Net::LDAP->new( $ldap_uri );
1322   }
1323   # Still not defined?
1324   if( ! defined $ldap_handle ) {
1325           daemon_log( "$session_id ERROR: ch $$: Net::LDAP constructor failed: $!\n" );
1326           return 0;
1327   }
1329   # Bind to ldap server - eventually authenticate
1330   if( defined $ldap_admin_dn ) {
1331     if( defined $ldap_admin_password ) {
1332       $mesg = $ldap_handle->bind( $ldap_admin_dn, password => $ldap_admin_password );
1333     } else {
1334       $mesg = $ldap_handle->bind( $ldap_admin_dn );
1335     }
1336   } else {
1337     $mesg = $ldap_handle->bind();
1338   }
1340   if( 0 != $mesg->code ) {
1341     undef( $ldap_handle ) if( 81 == $mesg->code );
1342     daemon_log( "$session_id ERROR: ch $$: LDAP bind: error (". $mesg->code . ') - ' . $mesg->error . "\n", 1);
1343     return 0;
1344   }
1345         daemon_log("$session_id DEBUG: create a new connection to URI '$ldap_uri'", 7);
1346   return 1;
1350 sub change_fai_state {
1351     my ($st, $targets, $session_id) = @_;
1352     $session_id = 0 if not defined $session_id;
1353     # Set FAI state to localboot
1354     my %mapActions= (
1355         reboot    => '',
1356         update    => 'softupdate',
1357         localboot => 'localboot',
1358         reinstall => 'install',
1359         rescan    => '',
1360         wake      => '',
1361         memcheck  => 'memcheck',
1362         sysinfo   => 'sysinfo',
1363         install   => 'install',
1364     );
1366     # Return if this is unknown
1367     if (!exists $mapActions{ $st }){
1368         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1369       return;
1370     }
1372     my $state= $mapActions{ $st };
1374     &refresh_ldap_handle();
1375     if( defined($ldap_handle) ) {
1377       # Build search filter for hosts
1378         my $search= "(&(objectClass=GOhard)";
1379         foreach (@{$targets}){
1380             $search.= "(macAddress=$_)";
1381         }
1382         $search.= ")";
1384       # If there's any host inside of the search string, procress them
1385         if (!($search =~ /macAddress/)){
1386             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1387             return;
1388         }
1390       # Perform search for Unit Tag
1391       my $mesg = $ldap_handle->search(
1392           base   => $ldap_base,
1393           scope  => 'sub',
1394           attrs  => ['dn', 'FAIstate', 'objectClass'],
1395           filter => "$search"
1396           );
1398       if ($mesg->count) {
1399         my @entries = $mesg->entries;
1400         foreach my $entry (@entries) {
1401           # Only modify entry if it is not set to '$state'
1402           if ($entry->get_value("FAIstate") ne "$state"){
1403             daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1404             my $result;
1405             my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1406             if (exists $tmp{'FAIobject'}){
1407               if ($state eq ''){
1408                 $result= $ldap_handle->modify($entry->dn, changes => [
1409                             delete => [ FAIstate => [] ] ]);
1410               } else {
1411                 $result= $ldap_handle->modify($entry->dn, changes => [
1412                             replace => [ FAIstate => $state ] ]);
1413               }
1414             } elsif ($state ne ''){
1415               $result= $ldap_handle->modify($entry->dn, changes => [
1416                           add     => [ objectClass => 'FAIobject' ],
1417                           add     => [ FAIstate => $state ] ]);
1418             }
1420             # Errors?
1421             if ($result->code){
1422               daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1423             }
1425           } else {
1426             daemon_log("$session_id DEBUG FAIstate at host '$_' already at state '$st'", 7); 
1427           }  
1428         }
1429       }
1430     # if no ldap handle defined
1431     } else {
1432         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1433     }
1437 sub change_goto_state {
1438     my ($st, $targets, $session_id) = @_;
1439     $session_id = 0  if not defined $session_id;
1441     # Switch on or off?
1442     my $state= $st eq 'active' ? 'active': 'locked';
1444     &refresh_ldap_handle();
1445     if( defined($ldap_handle) ) {
1447       # Build search filter for hosts
1448       my $search= "(&(objectClass=GOhard)";
1449       foreach (@{$targets}){
1450         $search.= "(macAddress=$_)";
1451       }
1452       $search.= ")";
1454       # If there's any host inside of the search string, procress them
1455       if (!($search =~ /macAddress/)){
1456         return;
1457       }
1459       # Perform search for Unit Tag
1460       my $mesg = $ldap_handle->search(
1461           base   => $ldap_base,
1462           scope  => 'sub',
1463           attrs  => ['dn', 'gotoMode'],
1464           filter => "$search"
1465           );
1467       if ($mesg->count) {
1468         my @entries = $mesg->entries;
1469         foreach my $entry (@entries) {
1471           # Only modify entry if it is not set to '$state'
1472           if ($entry->get_value("gotoMode") ne $state){
1474             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1475             my $result;
1476             $result= $ldap_handle->modify($entry->dn, changes => [
1477                                                 replace => [ gotoMode => $state ] ]);
1479             # Errors?
1480             if ($result->code){
1481               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1482             }
1484           }
1485         }
1486       }
1488     }
1492 sub create_fai_server_db {
1493     my ($table_name, $kernel) = @_;
1494         my $result;
1496         if(defined($ldap_handle)) {
1497                 daemon_log("INFO: create_fai_server_db: start", 5);
1498                 my $mesg= $ldap_handle->search(
1499                         base   => $ldap_base,
1500                         scope  => 'sub',
1501                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1502                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1503                 );
1504                 if($mesg->{'resultCode'} == 0 &&
1505                    $mesg->count != 0) {
1506                    foreach my $entry (@{$mesg->{entries}}) {
1507                            if($entry->exists('FAIrepository')) {
1508                                    # Add an entry for each Repository configured for server
1509                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1510                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1511                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1512                                                    $result= $fai_server_db->add_dbentry( { 
1513                                                                    table => $table_name,
1514                                                                    primkey => ['server', 'release', 'tag'],
1515                                                                    server => $tmp_url,
1516                                                                    release => $tmp_release,
1517                                                                    sections => $tmp_sections,
1518                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1519                                                            } );
1520                                            }
1521                                    }
1522                            }
1523                    }
1524                 daemon_log("INFO: create_fai_server_db: finished", 5);
1526                 # TODO: Find a way to post the 'create_packages_list_db' event
1527                 &create_packages_list_db();
1528         }       
1530         return $result;
1533 sub run_create_fai_server_db {
1534     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1535     my $task = POE::Wheel::Run->new(
1536             Program => sub { &create_fai_server_db($table_name,$kernel) },
1537             StdoutEvent  => "session_run_result",
1538             StderrEvent  => "session_run_debug",
1539             CloseEvent   => "session_run_done",
1540             );
1542     $heap->{task}->{ $task->ID } = $task;
1543     return;
1547 sub create_fai_release_db {
1548         my ($table_name) = @_;
1549         my $result;
1551         if(defined($ldap_handle)) {
1552                 daemon_log("INFO: create_fai_release_db: start",5);
1553                 my $mesg= $ldap_handle->search(
1554                         base   => $ldap_base,
1555                         scope  => 'sub',
1556                         attrs  => [],
1557                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1558                 );
1559                 if($mesg->{'resultCode'} == 0 &&
1560                         $mesg->count != 0) {
1561                         # Walk through all possible FAI container ou's
1562                         my @sql_list;
1563                         my $timestamp= &get_time();
1564                         foreach my $ou (@{$mesg->{entries}}) {
1565                                 my $tmp_classes= resolve_fai_classes($ou->dn);
1566                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1567                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1568                                         if(@tmp_array) {
1569                                                 foreach my $entry (@tmp_array) {
1570                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1571                                                                 my $sql= 
1572                                                                 "INSERT INTO $table_name "
1573                                                                 ."(timestamp, release, class, type, state) VALUES ("
1574                                                                 .$timestamp.","
1575                                                                 ."'".$entry->{'release'}."',"
1576                                                                 ."'".$entry->{'class'}."',"
1577                                                                 ."'".$entry->{'type'}."',"
1578                                                                 ."'".$entry->{'state'}."')";
1579                                                                 push @sql_list, $sql;
1580                                                         }
1581                                                 }
1582                                         }
1583                                 }
1584                         }
1585                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1586                         if(@sql_list) {
1587                                 unshift @sql_list, "DELETE FROM $table_name";
1588                                 $fai_server_db->exec_statementlist(\@sql_list);
1589                         }
1590                         daemon_log("DEBUG: Done with inserting",6);
1591                 }
1592                 daemon_log("INFO: create_fai_release_db: finished",5);
1593         }
1595         return $result;
1597 sub run_create_fai_release_db {
1598     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1599     my $task = POE::Wheel::Run->new(
1600             Program => sub { &create_fai_release_db($table_name) },
1601             StdoutEvent  => "session_run_result",
1602             StderrEvent  => "session_run_debug",
1603             CloseEvent   => "session_run_done",
1604             );
1606     $heap->{task}->{ $task->ID } = $task;
1607     return;
1610 sub get_fai_types {
1611         my $tmp_classes = shift || return undef;
1612         my @result;
1614         foreach my $type(keys %{$tmp_classes}) {
1615                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1616                         my $entry = {
1617                                 type => $type,
1618                                 state => $tmp_classes->{$type}[0],
1619                         };
1620                         push @result, $entry;
1621                 }
1622         }
1624         return @result;
1627 sub get_fai_state {
1628         my $result = "";
1629         my $tmp_classes = shift || return $result;
1631         foreach my $type(keys %{$tmp_classes}) {
1632                 if(defined($tmp_classes->{$type}[0])) {
1633                         $result = $tmp_classes->{$type}[0];
1634                         
1635                 # State is equal for all types in class
1636                         last;
1637                 }
1638         }
1640         return $result;
1643 sub resolve_fai_classes {
1644         my $result;
1645         my $fai_base= shift;
1646         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1647         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1648         my $fai_classes;
1650         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1651         my $mesg= $ldap_handle->search(
1652                 base   => $fai_base,
1653                 scope  => 'sub',
1654                 attrs  => ['cn','objectClass','FAIstate'],
1655                 filter => $fai_filter,
1656         );
1657         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1659         if($mesg->{'resultCode'} == 0 &&
1660                 $mesg->count != 0) {
1661                 foreach my $entry (@{$mesg->{entries}}) {
1662                         if($entry->exists('cn')) {
1663                                 my $tmp_dn= $entry->dn();
1665                                 # Skip classname and ou dn parts for class
1666                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1668                                 # Skip classes without releases
1669                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1670                                         next;
1671                                 }
1673                                 my $tmp_cn= $entry->get_value('cn');
1674                                 my $tmp_state= $entry->get_value('FAIstate');
1676                                 my $tmp_type;
1677                                 # Get FAI type
1678                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1679                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1680                                                 $tmp_type= $oclass;
1681                                                 last;
1682                                         }
1683                                 }
1685                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1686                                         # A Subrelease
1687                                         my @sub_releases = split(/,/, $tmp_release);
1689                                         # Walk through subreleases and build hash tree
1690                                         my $hash;
1691                                         while(my $tmp_sub_release = pop @sub_releases) {
1692                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1693                                         }
1694                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1695                                 } else {
1696                                         # A branch, no subrelease
1697                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1698                                 }
1699                         } elsif (!$entry->exists('cn')) {
1700                                 my $tmp_dn= $entry->dn();
1701                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1703                                 # Skip classes without releases
1704                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1705                                         next;
1706                                 }
1708                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1709                                         # A Subrelease
1710                                         my @sub_releases= split(/,/, $tmp_release);
1712                                         # Walk through subreleases and build hash tree
1713                                         my $hash;
1714                                         while(my $tmp_sub_release = pop @sub_releases) {
1715                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1716                                         }
1717                                         # Remove the last two characters
1718                                         chop($hash);
1719                                         chop($hash);
1721                                         eval('$fai_classes->'.$hash.'= {}');
1722                                 } else {
1723                                         # A branch, no subrelease
1724                                         if(!exists($fai_classes->{$tmp_release})) {
1725                                                 $fai_classes->{$tmp_release} = {};
1726                                         }
1727                                 }
1728                         }
1729                 }
1731                 # The hash is complete, now we can honor the copy-on-write based missing entries
1732                 foreach my $release (keys %$fai_classes) {
1733                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1734                 }
1735         }
1736         return $result;
1739 sub apply_fai_inheritance {
1740        my $fai_classes = shift || return {};
1741        my $tmp_classes;
1743        # Get the classes from the branch
1744        foreach my $class (keys %{$fai_classes}) {
1745                # Skip subreleases
1746                if($class =~ /^ou=.*$/) {
1747                        next;
1748                } else {
1749                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1750                }
1751        }
1753        # Apply to each subrelease
1754        foreach my $subrelease (keys %{$fai_classes}) {
1755                if($subrelease =~ /ou=/) {
1756                        foreach my $tmp_class (keys %{$tmp_classes}) {
1757                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1758                                        $fai_classes->{$subrelease}->{$tmp_class} =
1759                                        deep_copy($tmp_classes->{$tmp_class});
1760                                } else {
1761                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1762                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1763                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1764                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1765                                                }
1766                                        }
1767                                }
1768                        }
1769                }
1770        }
1772        # Find subreleases in deeper levels
1773        foreach my $subrelease (keys %{$fai_classes}) {
1774                if($subrelease =~ /ou=/) {
1775                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1776                                if($subsubrelease =~ /ou=/) {
1777                                        apply_fai_inheritance($fai_classes->{$subrelease});
1778                                }
1779                        }
1780                }
1781        }
1783        return $fai_classes;
1786 sub get_fai_release_entries {
1787         my $tmp_classes = shift || return;
1788         my $parent = shift || "";
1789         my @result = shift || ();
1791         foreach my $entry (keys %{$tmp_classes}) {
1792                 if(defined($entry)) {
1793                         if($entry =~ /^ou=.*$/) {
1794                                 my $release_name = $entry;
1795                                 $release_name =~ s/ou=//g;
1796                                 if(length($parent)>0) {
1797                                         $release_name = $parent."/".$release_name;
1798                                 }
1799                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1800                                 foreach my $bufentry(@bufentries) {
1801                                         push @result, $bufentry;
1802                                 }
1803                         } else {
1804                                 my @types = get_fai_types($tmp_classes->{$entry});
1805                                 foreach my $type (@types) {
1806                                         push @result, 
1807                                         {
1808                                                 'class' => $entry,
1809                                                 'type' => $type->{'type'},
1810                                                 'release' => $parent,
1811                                                 'state' => $type->{'state'},
1812                                         };
1813                                 }
1814                         }
1815                 }
1816         }
1818         return @result;
1821 sub deep_copy {
1822         my $this = shift;
1823         if (not ref $this) {
1824                 $this;
1825         } elsif (ref $this eq "ARRAY") {
1826                 [map deep_copy($_), @$this];
1827         } elsif (ref $this eq "HASH") {
1828                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1829         } else { die "what type is $_?" }
1833 sub session_run_result {
1834     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1835     $kernel->sig(CHLD => "child_reap");
1838 sub session_run_debug {
1839     my $result = $_[ARG0];
1840     print STDERR "$result\n";
1843 sub session_run_done {
1844     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1845     delete $heap->{task}->{$task_id};
1848 sub create_sources_list {
1849         my $result="/tmp/gosa_si_tmp_sources_list";
1851         # Remove old file
1852         if(stat($result)) {
1853                 unlink($result);
1854         }
1856         my $fh;
1857         open($fh, ">$result") or return undef;
1858         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1859                 my $mesg=$ldap_handle->search(
1860                                 base    => $ldap_server_dn,
1861                                 scope   => 'base',
1862                                 attrs   => 'FAIrepository',
1863                                 filter  => 'objectClass=FAIrepositoryServer'
1864                                 );
1865                 if($mesg->count) {
1866                         foreach my $entry(@{$mesg->{'entries'}}) {
1867                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1868                                 my $line = "deb $server $release";
1869                                 $sections =~ s/,/ /g;
1870                                 $line.= " $sections";
1871                                 print $fh $line."\n";
1872                         }
1873                 }
1874         }
1875         close($fh);
1877         return $result;
1880 sub create_packages_list_db {
1881     my ($sources_file) = @_ || &create_sources_list;
1882     my $line;
1883     daemon_log("INFO: create_packages_list_db: start", 5); 
1885     open(CONFIG, "<$sources_file") or do {
1886         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1887         return;
1888     };
1889     
1890     # Read lines
1891     while ($line = <CONFIG>){
1892         # Unify
1893         chop($line);
1894         $line =~ s/^\s+//;
1895         $line =~ s/^\s+/ /;
1897         # Strip comments
1898         $line =~ s/#.*$//g;
1900         # Skip empty lines
1901         if ($line =~ /^\s*$/){
1902             next;
1903         }
1905         # Interpret deb line
1906         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1907             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1908             my $section;
1909             foreach $section (split(' ', $sections)){
1910                 &parse_package_info( $baseurl, $dist, $section );
1911             }
1912         }
1913     }
1915     close (CONFIG);
1917     daemon_log("INFO: create_packages_list_db: finished", 5); 
1918     return;
1921 sub run_create_packages_list_db {
1922     my ($session, $heap) = @_[SESSION, HEAP];
1923     my $task = POE::Wheel::Run->new(
1924             Program => sub {&create_packages_list_db},
1925             StdoutEvent  => "session_run_result",
1926             StderrEvent  => "session_run_debug",
1927             CloseEvent   => "session_run_done",
1928             );
1929     $heap->{task}->{ $task->ID } = $task;
1932 sub parse_package_info {
1933   my ($baseurl, $dist, $section)= @_;
1934   my ($package);
1936   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1937   $repo_dirs{ "${repo_path}/pool" } = 1;
1939   foreach $package ("Packages.gz"){
1940     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1941     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1942     parse_package( "$outdir/$dist/$section", $dist, $path );
1943   }
1944   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1947 sub get_package {
1948   my ($url, $dest)= @_;
1950   my $tpath = dirname($dest);
1951   -d "$tpath" || mkpath "$tpath";
1953   # This is ugly, but I've no time to take a look at "how it works in perl"
1954   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1955       system("gunzip -cd '$dest' > '$dest.in'");
1956       unlink($dest);
1957   } else {
1958       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1959   }
1960   return 0;
1963 sub parse_package {
1964     my ($path, $dist, $srv_path)= @_;
1965     my ($package, $version, $section, $description);
1966     my @sql_list;
1967     my $PACKAGES;
1969     if(not stat("$path.in")) {
1970         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1971         return;
1972     }
1974     open($PACKAGES, "<$path.in");
1975         if(not defined($PACKAGES)) {
1976         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
1977         return;
1978     }
1980     # Read lines
1981     while (<$PACKAGES>){
1982         my $line = $_;
1983         # Unify
1984         chop($line);
1986         # Use empty lines as a trigger
1987         if ($line =~ /^\s*$/){
1988             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1989             push(@sql_list, $sql);
1990             $package = "none";
1991             $version = "none";
1992             $section = "none";
1993             $description = "none"; 
1994             next;
1995         }
1997         # Trigger for package name
1998         if ($line =~ /^Package:\s/){
1999             ($package)= ($line =~ /^Package: (.*)$/);
2000             next;
2001         }
2003         # Trigger for version
2004         if ($line =~ /^Version:\s/){
2005             ($version)= ($line =~ /^Version: (.*)$/);
2006             next;
2007         }
2009         # Trigger for description
2010         if ($line =~ /^Description:\s/){
2011             ($description)= ($line =~ /^Description: (.*)$/);
2012             next;
2013         }
2015         # Trigger for section
2016         if ($line =~ /^Section:\s/){
2017             ($section)= ($line =~ /^Section: (.*)$/);
2018             next;
2019         }
2021         # Trigger for filename
2022         if ($line =~ /^Filename:\s/){
2023                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2024                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2025                 next;
2026         }
2027     }
2029     close( $PACKAGES );
2030     unlink( "$path.in" );
2031     
2032     $packages_list_db->exec_statementlist(\@sql_list);
2035 sub store_fileinfo {
2036   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2038   my %fileinfo = (
2039     'package' => $package,
2040     'dist' => $dist,
2041     'version' => $vers,
2042   );
2044   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2047 sub cleanup_and_extract {
2048   my $fileinfo = $repo_files{ $File::Find::name };
2050   if( defined $fileinfo ) {
2052     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2053     my $sql;
2054     my $package = $fileinfo->{ 'package' };
2055     my $newver = $fileinfo->{ 'version' };
2057     mkpath($dir);
2058     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2060     if( -f "$dir/DEBIAN/templates" ) {
2062       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2064       my $tmpl= "";
2065       {
2066           local $/=undef;
2067           open FILE, "$dir/DEBIAN/templates";
2068           $tmpl = &encode_base64(<FILE>);
2069           close FILE;
2070       }
2071       rmtree("$dir/DEBIAN/templates");
2073       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2075     } else {
2076       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2077     }
2079     my $res= $main::packages_list_db->update_dbentry($sql);
2080   }
2084 #==== MAIN = main ==============================================================
2085 #  parse commandline options
2086 Getopt::Long::Configure( "bundling" );
2087 GetOptions("h|help" => \&usage,
2088         "c|config=s" => \$cfg_file,
2089         "f|foreground" => \$foreground,
2090         "v|verbose+" => \$verbose,
2091         "no-bus+" => \$no_bus,
2092         "no-arp+" => \$no_arp,
2093            );
2095 #  read and set config parameters
2096 &check_cmdline_param ;
2097 &read_configfile;
2098 &check_pid;
2100 $SIG{CHLD} = 'IGNORE';
2102 # forward error messages to logfile
2103 if( ! $foreground ) {
2104   open( STDIN,  '+>/dev/null' );
2105   open( STDOUT, '+>&STDIN'    );
2106   open( STDERR, '+>&STDIN'    );
2109 # Just fork, if we are not in foreground mode
2110 if( ! $foreground ) { 
2111     chdir '/'                 or die "Can't chdir to /: $!";
2112     $pid = fork;
2113     setsid                    or die "Can't start a new session: $!";
2114     umask 0;
2115 } else { 
2116     $pid = $$; 
2119 # Do something useful - put our PID into the pid_file
2120 if( 0 != $pid ) {
2121     open( LOCK_FILE, ">$pid_file" );
2122     print LOCK_FILE "$pid\n";
2123     close( LOCK_FILE );
2124     if( !$foreground ) { 
2125         exit( 0 ) 
2126     };
2129 daemon_log(" ", 1);
2130 daemon_log("$0 started!", 1);
2132 if ($no_bus > 0) {
2133     $bus_activ = "false"
2138 # delete old DBsqlite lock files
2139 #unlink('/tmp/gosa_si_lock*');
2141 # connect to gosa-si job queue
2142 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2143 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2145 # connect to known_clients_db
2146 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2147 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2149 # connect to known_server_db
2150 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2151 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2153 # connect to login_usr_db
2154 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2155 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2157 # connect to fai_server_db and fai_release_db
2158 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2159 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2160 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2162 # connect to packages_list_db
2163 unlink($packages_list_file_name);
2164 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2165 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2167 # connect to messaging_db
2168 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2169 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2172 # create xml object used for en/decrypting
2173 $xml = new XML::Simple();
2175 # create socket for incoming xml messages
2177 POE::Component::Server::TCP->new(
2178         Port => $server_port,
2179         ClientInput => sub {
2180         my ($kernel, $input) = @_[KERNEL, ARG0];
2181         push(@tasks, $input);
2182         $kernel->yield("next_task");
2183         },
2184     InlineStates => {
2185         next_task => \&next_task,
2186         task_result => \&handle_task_result,
2187         task_done   => \&handle_task_done,
2188         task_debug  => \&handle_task_debug,
2189         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2190     }
2191 );
2193 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2195 # create session for repeatedly checking the job queue for jobs
2196 POE::Session->create(
2197         inline_states => {
2198                 _start => \&_start,
2199                 sig_handler => \&sig_handler,
2200                 watch_for_new_jobs => \&watch_for_new_jobs,
2201         watch_for_done_jobs => \&watch_for_done_jobs,
2202         create_packages_list_db => \&run_create_packages_list_db,
2203         create_fai_server_db => \&run_create_fai_server_db,
2204         create_fai_release_db => \&run_create_fai_release_db,
2205         session_run_result => \&session_run_result,
2206         session_run_debug => \&session_run_debug,
2207         session_run_done => \&session_run_done,
2208         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2209         }
2210 );
2213 # import all modules
2214 &import_modules;
2216 # check wether all modules are gosa-si valid passwd check
2218 POE::Kernel->run();
2219 exit;