Code

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