Code

Fixed sources_list creation.
[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);
94 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
97 # specifies the verbosity of the daemon_log
98 $verbose = 0 ;
100 # if foreground is not null, script will be not forked to background
101 $foreground = 0 ;
103 # specifies the timeout seconds while checking the online status of a registrating client
104 $ping_timeout = 5;
106 $no_bus = 0;
107 $bus_activ = "true";
109 $no_arp = 0;
111 our $prg= basename($0);
113 # holds all gosa jobs
114 our $job_db;
115 our $job_queue_tn = 'jobs';
116 my $job_queue_file_name;
117 my @job_queue_col_names = ("id INTEGER", 
118                 "timestamp", 
119                 "status DEFAULT 'none'", 
120                 "result DEFAULT 'none'", 
121                 "progress DEFAULT 'none'", 
122                 "headertag DEFAULT 'none'", 
123                 "targettag DEFAULT 'none'", 
124                 "xmlmessage DEFAULT 'none'", 
125                 "macaddress DEFAULT 'none'",
126                 );
128 # holds all other gosa-sd as well as the gosa-sd-bus
129 our $known_server_db;
130 our $known_server_tn = "known_server";
131 my $known_server_file_name;
132 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
134 # holds all registrated clients
135 our $known_clients_db;
136 our $known_clients_tn = "known_clients";
137 my $known_clients_file_name;
138 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
140 # holds all logged in user at each client 
141 our $login_users_db;
142 our $login_users_tn = "login_users";
143 my $login_users_file_name;
144 my @login_users_col_names = ('client', 'user', 'timestamp');
146 # holds all fai server, the debian release and tag
147 our $fai_server_db;
148 our $fai_server_tn = "fai_server"; 
149 my $fai_server_file_name;
150 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag'); 
151 our $fai_release_tn = "fai_release"; 
152 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state'); 
154 # holds all packages available from different repositories
155 our $packages_list_db;
156 our $packages_list_tn = "packages_list";
157 my $packages_list_file_name;
158 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
159 my $outdir = "/tmp/packages_list_db";
160 my $arch = "i386"; 
162 # holds all messages which should be delivered to a user
163 our $messaging_db;
164 our $messaging_tn = "messaging"; 
165 our @messaging_col_names = ('subject', 'from', 'to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER', );
166 my $messaging_file_name;
168 # path to directory to store client install log files
169 our $client_fai_log_dir = "/var/log/fai"; 
171 # queue which stores taskes until one of the $max_children children are ready to process the task
172 my @tasks = qw();
173 my $max_children = 2;
176 %cfg_defaults = (
177 "general" => {
178     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
179     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
180     },
181 "bus" => {
182     "activ" => [\$bus_activ, "true"],
183     },
184 "server" => {
185     "port" => [\$server_port, "20081"],
186     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
187     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
188     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
189     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
190     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
191     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
192     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
193     "repo-path" => [\$repo_path, '/srv/www/repository'],
194     "ldap-uri" => [\$ldap_uri, ""],
195     "ldap-base" => [\$ldap_base, ""],
196     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
197     "ldap-admin-password" => [\$ldap_admin_password, ""],
198     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
199     "max-clients" => [\$max_clients, 10],
200     },
201 "GOsaPackages" => {
202     "ip" => [\$gosa_ip, "0.0.0.0"],
203     "port" => [\$gosa_port, "20082"],
204     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
205     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
206     "key" => [\$GosaPackages_key, "none"],
207     },
208 "SIPackages" => {
209     "key" => [\$SIPackages_key, "none"],
210     },
211 );
214 #===  FUNCTION  ================================================================
215 #         NAME:  usage
216 #   PARAMETERS:  nothing
217 #      RETURNS:  nothing
218 #  DESCRIPTION:  print out usage text to STDERR
219 #===============================================================================
220 sub usage {
221     print STDERR << "EOF" ;
222 usage: $prg [-hvf] [-c config]
224            -h        : this (help) message
225            -c <file> : config file
226            -f        : foreground, process will not be forked to background
227            -v        : be verbose (multiple to increase verbosity)
228            -no-bus   : starts $prg without connection to bus
229            -no-arp   : starts $prg without connection to arp module
230  
231 EOF
232     print "\n" ;
236 #===  FUNCTION  ================================================================
237 #         NAME:  read_configfile
238 #   PARAMETERS:  cfg_file - string -
239 #      RETURNS:  nothing
240 #  DESCRIPTION:  read cfg_file and set variables
241 #===============================================================================
242 sub read_configfile {
243     my $cfg;
244     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
245         if( -r $cfg_file ) {
246             $cfg = Config::IniFiles->new( -file => $cfg_file );
247         } else {
248             print STDERR "Couldn't read config file!\n";
249         }
250     } else {
251         $cfg = Config::IniFiles->new() ;
252     }
253     foreach my $section (keys %cfg_defaults) {
254         foreach my $param (keys %{$cfg_defaults{ $section }}) {
255             my $pinfo = $cfg_defaults{ $section }{ $param };
256             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
257         }
258     }
262 #===  FUNCTION  ================================================================
263 #         NAME:  logging
264 #   PARAMETERS:  level - string - default 'info'
265 #                msg - string -
266 #                facility - string - default 'LOG_DAEMON'
267 #      RETURNS:  nothing
268 #  DESCRIPTION:  function for logging
269 #===============================================================================
270 sub daemon_log {
271     # log into log_file
272     my( $msg, $level ) = @_;
273     if(not defined $msg) { return }
274     if(not defined $level) { $level = 1 }
275     if(defined $log_file){
276         open(LOG_HANDLE, ">>$log_file");
277         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
278             print STDERR "cannot open $log_file: $!";
279             return }
280             chomp($msg);
281             if($level <= $verbose){
282                 my ($seconds, $minutes, $hours, $monthday, $month,
283                         $year, $weekday, $yearday, $sommertime) = localtime(time);
284                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
285                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
286                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
287                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
288                 $month = $monthnames[$month];
289                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
290                 $year+=1900;
291                 my $name = $prg;
293                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
294                 print LOG_HANDLE $log_msg;
295                 if( $foreground ) { 
296                     print STDERR $log_msg;
297                 }
298             }
299         close( LOG_HANDLE );
300     }
304 #===  FUNCTION  ================================================================
305 #         NAME:  check_cmdline_param
306 #   PARAMETERS:  nothing
307 #      RETURNS:  nothing
308 #  DESCRIPTION:  validates commandline parameter
309 #===============================================================================
310 sub check_cmdline_param () {
311     my $err_config;
312     my $err_counter = 0;
313         if(not defined($cfg_file)) {
314                 $cfg_file = "/etc/gosa-si/server.conf";
315                 if(! -r $cfg_file) {
316                         $err_config = "please specify a config file";
317                         $err_counter += 1;
318                 }
319     }
320     if( $err_counter > 0 ) {
321         &usage( "", 1 );
322         if( defined( $err_config)) { print STDERR "$err_config\n"}
323         print STDERR "\n";
324         exit( -1 );
325     }
329 #===  FUNCTION  ================================================================
330 #         NAME:  check_pid
331 #   PARAMETERS:  nothing
332 #      RETURNS:  nothing
333 #  DESCRIPTION:  handels pid processing
334 #===============================================================================
335 sub check_pid {
336     $pid = -1;
337     # Check, if we are already running
338     if( open(LOCK_FILE, "<$pid_file") ) {
339         $pid = <LOCK_FILE>;
340         if( defined $pid ) {
341             chomp( $pid );
342             if( -f "/proc/$pid/stat" ) {
343                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
344                 if( $stat ) {
345                                         daemon_log("ERROR: Already running",1);
346                     close( LOCK_FILE );
347                     exit -1;
348                 }
349             }
350         }
351         close( LOCK_FILE );
352         unlink( $pid_file );
353     }
355     # create a syslog msg if it is not to possible to open PID file
356     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
357         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
358         if (open(LOCK_FILE, '<', $pid_file)
359                 && ($pid = <LOCK_FILE>))
360         {
361             chomp($pid);
362             $msg .= "(PID $pid)\n";
363         } else {
364             $msg .= "(unable to read PID)\n";
365         }
366         if( ! ($foreground) ) {
367             openlog( $0, "cons,pid", "daemon" );
368             syslog( "warning", $msg );
369             closelog();
370         }
371         else {
372             print( STDERR " $msg " );
373         }
374         exit( -1 );
375     }
378 #===  FUNCTION  ================================================================
379 #         NAME:  import_modules
380 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
381 #                are stored
382 #      RETURNS:  nothing
383 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
384 #                state is on is imported by "require 'file';"
385 #===============================================================================
386 sub import_modules {
387     daemon_log(" ", 1);
389     if (not -e $modules_path) {
390         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
391     }
393     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
394     while (defined (my $file = readdir (DIR))) {
395         if (not $file =~ /(\S*?).pm$/) {
396             next;
397         }
398                 my $mod_name = $1;
400         if( $file =~ /ArpHandler.pm/ ) {
401             if( $no_arp > 0 ) {
402                 next;
403             }
404         }
405         
406         eval { require $file; };
407         if ($@) {
408             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
409             daemon_log("$@", 5);
410                 } else {
411                         my $info = eval($mod_name.'::get_module_info()');
412                         # Only load module if get_module_info() returns a non-null object
413                         if( $info ) {
414                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
415                                 $known_modules->{$mod_name} = $info;
416                                 daemon_log("INFO: module $mod_name loaded", 5);
417                         }
418                 }
419     }   
420     close (DIR);
424 #===  FUNCTION  ================================================================
425 #         NAME:  sig_int_handler
426 #   PARAMETERS:  signal - string - signal arose from system
427 #      RETURNS:  noting
428 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
429 #===============================================================================
430 sub sig_int_handler {
431     my ($signal) = @_;
433 #       if (defined($ldap_handle)) {
434 #               $ldap_handle->disconnect;
435 #       }
436     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
437     
439     daemon_log("shutting down gosa-si-server", 1);
440     system("kill `ps -C gosa-si-server -o pid=`");
442 $SIG{INT} = \&sig_int_handler;
445 sub check_key_and_xml_validity {
446     my ($crypted_msg, $module_key, $session_id) = @_;
447     my $msg;
448     my $msg_hash;
449     my $error_string;
450     eval{
451         $msg = &decrypt_msg($crypted_msg, $module_key);
453         if ($msg =~ /<xml>/i){
454             $msg =~ s/\s+/ /g;  # just for better daemon_log
455             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
456             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
458             ##############
459             # check header
460             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
461             my $header_l = $msg_hash->{'header'};
462             if( 1 > @{$header_l} ) { die 'empty header tag'; }
463             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
464             my $header = @{$header_l}[0];
465             if( 0 == length $header) { die 'empty string in header tag'; }
467             ##############
468             # check source
469             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
470             my $source_l = $msg_hash->{'source'};
471             if( 1 > @{$source_l} ) { die 'empty source tag'; }
472             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
473             my $source = @{$source_l}[0];
474             if( 0 == length $source) { die 'source error'; }
476             ##############
477             # check target
478             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
479             my $target_l = $msg_hash->{'target'};
480             if( 1 > @{$target_l} ) { die 'empty target tag'; }
481         }
482     };
483     if($@) {
484         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
485         $msg = undef;
486         $msg_hash = undef;
487     }
489     return ($msg, $msg_hash);
493 sub check_outgoing_xml_validity {
494     my ($msg) = @_;
496     my $msg_hash;
497     eval{
498         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
500         ##############
501         # check header
502         my $header_l = $msg_hash->{'header'};
503         if( 1 != @{$header_l} ) {
504             die 'no or more than one headers specified';
505         }
506         my $header = @{$header_l}[0];
507         if( 0 == length $header) {
508             die 'header has length 0';
509         }
511         ##############
512         # check source
513         my $source_l = $msg_hash->{'source'};
514         if( 1 != @{$source_l} ) {
515             die 'no or more than 1 sources specified';
516         }
517         my $source = @{$source_l}[0];
518         if( 0 == length $source) {
519             die 'source has length 0';
520         }
521         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
522                 $source =~ /^GOSA$/i ) {
523             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
524         }
525         
526         ##############
527         # check target  
528         my $target_l = $msg_hash->{'target'};
529         if( 0 == @{$target_l} ) {
530             die "no targets specified";
531         }
532         foreach my $target (@$target_l) {
533             if( 0 == length $target) {
534                 die "target has length 0";
535             }
536             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
537                     $target =~ /^GOSA$/i ||
538                     $target =~ /^\*$/ ||
539                     $target =~ /KNOWN_SERVER/i ||
540                     $target =~ /JOBDB/i ||
541                     $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 ){
542                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
543             }
544         }
545     };
546     if($@) {
547         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
548         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
549         $msg_hash = undef;
550     }
552     return ($msg_hash);
556 sub input_from_known_server {
557     my ($input, $remote_ip, $session_id) = @_ ;  
558     my ($msg, $msg_hash, $module);
560     my $sql_statement= "SELECT * FROM known_server";
561     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
563     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
564         my $host_name = $hit->{hostname};
565         if( not $host_name =~ "^$remote_ip") {
566             next;
567         }
568         my $host_key = $hit->{hostkey};
569         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
570         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
572         # check if module can open msg envelope with module key
573         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
574         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
575             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
576             daemon_log("$@", 8);
577             next;
578         }
579         else {
580             $msg = $tmp_msg;
581             $msg_hash = $tmp_msg_hash;
582             $module = "SIPackages";
583             last;
584         }
585     }
587     if( (!$msg) || (!$msg_hash) || (!$module) ) {
588         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
589     }
590   
591     return ($msg, $msg_hash, $module);
595 sub input_from_known_client {
596     my ($input, $remote_ip, $session_id) = @_ ;  
597     my ($msg, $msg_hash, $module);
599     my $sql_statement= "SELECT * FROM known_clients";
600     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
601     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
602         my $host_name = $hit->{hostname};
603         if( not $host_name =~ /^$remote_ip:\d*$/) {
604                 next;
605                 }
606         my $host_key = $hit->{hostkey};
607         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
608         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
610         # check if module can open msg envelope with module key
611         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
613         if( (!$msg) || (!$msg_hash) ) {
614             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
615             &daemon_log("$@", 8);
616             next;
617         }
618         else {
619             $module = "SIPackages";
620             last;
621         }
622     }
624     if( (!$msg) || (!$msg_hash) || (!$module) ) {
625         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
626     }
628     return ($msg, $msg_hash, $module);
632 sub input_from_unknown_host {
633     no strict "refs";
634     my ($input, $session_id) = @_ ;
635     my ($msg, $msg_hash, $module);
636     my $error_string;
637     
638         my %act_modules = %$known_modules;
640         while( my ($mod, $info) = each(%act_modules)) {
642         # check a key exists for this module
643         my $module_key = ${$mod."_key"};
644         if( not defined $module_key ) {
645             if( $mod eq 'ArpHandler' ) {
646                 next;
647             }
648             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
649             next;
650         }
651         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
653         # check if module can open msg envelope with module key
654         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
655         if( (not defined $msg) || (not defined $msg_hash) ) {
656             next;
657         }
658         else {
659             $module = $mod;
660             last;
661         }
662     }
664     if( (!$msg) || (!$msg_hash) || (!$module)) {
665         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
666     }
668     return ($msg, $msg_hash, $module);
672 sub create_ciphering {
673     my ($passwd) = @_;
674         if((!defined($passwd)) || length($passwd)==0) {
675                 $passwd = "";
676         }
677     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
678     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
679     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
680     $my_cipher->set_iv($iv);
681     return $my_cipher;
685 sub encrypt_msg {
686     my ($msg, $key) = @_;
687     my $my_cipher = &create_ciphering($key);
688     my $len;
689     {
690             use bytes;
691             $len= 16-length($msg)%16;
692     }
693     $msg = "\0"x($len).$msg;
694     $msg = $my_cipher->encrypt($msg);
695     chomp($msg = &encode_base64($msg));
696     # there are no newlines allowed inside msg
697     $msg=~ s/\n//g;
698     return $msg;
702 sub decrypt_msg {
704     my ($msg, $key) = @_ ;
705     $msg = &decode_base64($msg);
706     my $my_cipher = &create_ciphering($key);
707     $msg = $my_cipher->decrypt($msg); 
708     $msg =~ s/\0*//g;
709     return $msg;
713 sub get_encrypt_key {
714     my ($target) = @_ ;
715     my $encrypt_key;
716     my $error = 0;
718     # target can be in known_server
719     if( not defined $encrypt_key ) {
720         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
721         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
722         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
723             my $host_name = $hit->{hostname};
724             if( $host_name ne $target ) {
725                 next;
726             }
727             $encrypt_key = $hit->{hostkey};
728             last;
729         }
730     }
732     # target can be in known_client
733     if( not defined $encrypt_key ) {
734         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
735         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
736         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
737             my $host_name = $hit->{hostname};
738             if( $host_name ne $target ) {
739                 next;
740             }
741             $encrypt_key = $hit->{hostkey};
742             last;
743         }
744     }
746     return $encrypt_key;
750 #===  FUNCTION  ================================================================
751 #         NAME:  open_socket
752 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
753 #                [PeerPort] string necessary if port not appended by PeerAddr
754 #      RETURNS:  socket IO::Socket::INET
755 #  DESCRIPTION:  open a socket to PeerAddr
756 #===============================================================================
757 sub open_socket {
758     my ($PeerAddr, $PeerPort) = @_ ;
759     if(defined($PeerPort)){
760         $PeerAddr = $PeerAddr.":".$PeerPort;
761     }
762     my $socket;
763     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
764             Porto => "tcp",
765             Type => SOCK_STREAM,
766             Timeout => 5,
767             );
768     if(not defined $socket) {
769         return;
770     }
771 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
772     return $socket;
776 #===  FUNCTION  ================================================================
777 #         NAME:  get_ip 
778 #   PARAMETERS:  interface name (i.e. eth0)
779 #      RETURNS:  (ip address) 
780 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
781 #===============================================================================
782 sub get_ip {
783         my $ifreq= shift;
784         my $result= "";
785         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
786         my $proto= getprotobyname('ip');
788         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
789                 or die "socket: $!";
791         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
792                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
793                 my ($port, $addr) = sockaddr_in $sin;
794                 my $ip            = inet_ntoa $addr;
796                 if ($ip && length($ip) > 0) {
797                         $result = $ip;
798                 }
799         }
801         return $result;
805 sub get_local_ip_for_remote_ip {
806         my $remote_ip= shift;
807         my $result="0.0.0.0";
809         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
810                 if($remote_ip eq "127.0.0.1") {
811                         $result = "127.0.0.1";
812                 } else {
813                         my $PROC_NET_ROUTE= ('/proc/net/route');
815                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
816                                 or die "Could not open $PROC_NET_ROUTE";
818                         my @ifs = <PROC_NET_ROUTE>;
820                         close(PROC_NET_ROUTE);
822                         # Eat header line
823                         shift @ifs;
824                         chomp @ifs;
825                         foreach my $line(@ifs) {
826                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
827                                 my $destination;
828                                 my $mask;
829                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
830                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
831                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
832                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
833                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
834                                         # destination matches route, save mac and exit
835                                         $result= &get_ip($Iface);
836                                         last;
837                                 }
838                         }
839                 }
840         } else {
841                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
842         }
843         return $result;
847 sub send_msg_to_target {
848     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
849     my $error = 0;
850     my $header;
851     my $new_status;
852     my $act_status;
853     my ($sql_statement, $res);
854   
855     if( $msg_header ) {
856         $header = "'$msg_header'-";
857     } else {
858         $header = "";
859     }
861         # Patch the source ip
862         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
863                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
864                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
865         }
867     # encrypt xml msg
868     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
870     # opensocket
871     my $socket = &open_socket($address);
872     if( !$socket ) {
873         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
874         $error++;
875     }
876     
877     if( $error == 0 ) {
878         # send xml msg
879         print $socket $crypted_msg."\n";
881         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
882         #daemon_log("DEBUG: message:\n$msg", 9);
883         
884     }
886     # close socket in any case
887     if( $socket ) {
888         close $socket;
889     }
891     if( $error > 0 ) { $new_status = "down"; }
892     else { $new_status = $msg_header; }
895     # known_clients
896     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
897     $res = $known_clients_db->select_dbentry($sql_statement);
898     if( keys(%$res) > 0) {
899         $act_status = $res->{1}->{'status'};
900         if( $act_status eq "down" ) {
901             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
902             $res = $known_clients_db->del_dbentry($sql_statement);
903             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
904         } else { 
905             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
906             $res = $known_clients_db->update_dbentry($sql_statement);
907             if($new_status eq "down"){
908                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
909             } else {
910                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
911             }
912         }
913     }
915     # known_server
916     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
917     $res = $known_server_db->select_dbentry($sql_statement);
918     if( keys(%$res) > 0 ) {
919         $act_status = $res->{1}->{'status'};
920         if( $act_status eq "down" ) {
921             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
922             $res = $known_server_db->del_dbentry($sql_statement);
923             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
924         } 
925         else { 
926             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
927             $res = $known_server_db->update_dbentry($sql_statement);
928             if($new_status eq "down"){
929                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
930             }
931             else {
932                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
933             }
934         }
935     }
936     return $error; 
940 sub update_jobdb_status_for_send_msgs {
941     my ($answer, $error) = @_;
942     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
943         my $jobdb_id = $1;
944             
945         # sending msg faild
946         if( $error ) {
947             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
948                 my $sql_statement = "UPDATE $job_queue_tn ".
949                     "SET status='error', result='can not deliver msg, please consult log file' ".
950                     "WHERE id='$jobdb_id'";
951                 my $res = $job_db->update_dbentry($sql_statement);
952             }
954         # sending msg was successful
955         } else {
956             my $sql_statement = "UPDATE $job_queue_tn ".
957                 "SET status='done' ".
958                 "WHERE id='$jobdb_id' AND status='processed'";
959             my $res = $job_db->update_dbentry($sql_statement);
960         }
961     }
964 sub _start {
965     my ($kernel) = $_[KERNEL];
966     &trigger_db_loop($kernel);
967     $global_kernel = $kernel;
968         $kernel->yield('create_fai_server_db', $fai_server_tn );
969         $kernel->yield('create_fai_release_db', $fai_release_tn );
970         $kernel->sig(USR1 => "sig_handler");
971         $kernel->sig(USR2 => "create_packages_list_db");
974 sub sig_handler {
975         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
976         daemon_log("0 INFO got signal '$signal'", 1); 
977         $kernel->sig_handled();
978         return;
981 sub next_task {
982     my ($session, $heap) = @_[SESSION, HEAP];
984     while ( keys( %{ $heap->{task} } ) < $max_children ) {
985         my $next_task = shift @tasks;
986         last unless defined $next_task;
988         my $task = POE::Wheel::Run->new(
989                 Program => sub { process_task($session, $heap, $next_task) },
990                 StdioFilter => POE::Filter::Reference->new(),
991                 StdoutEvent  => "task_result",
992                 StderrEvent  => "task_debug",
993                 CloseEvent   => "task_done",
994                );
996         $heap->{task}->{ $task->ID } = $task;
997     }
1000 sub handle_task_result {
1001     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1002     my $client_answer = $result->{'answer'};
1003     if( $client_answer =~ s/session_id=(\d+)$// ) {
1004         my $session_id = $1;
1005         if( defined $session_id ) {
1006             my $session_reference = $kernel->ID_id_to_session($session_id);
1007             if( defined $session_reference ) {
1008                 $heap = $session_reference->get_heap();
1009             }
1010         }
1012         if(exists $heap->{'client'}) {
1013             $heap->{'client'}->put($client_answer);
1014         }
1015     }
1016     $kernel->sig(CHLD => "child_reap");
1019 sub handle_task_debug {
1020     my $result = $_[ARG0];
1021     print STDERR "$result\n";
1024 sub handle_task_done {
1025     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1026     delete $heap->{task}->{$task_id};
1027     $kernel->yield("next_task");
1030 sub process_task {
1031     no strict "refs";
1032     my ($session, $heap, $input) = @_;
1033     my $session_id = $session->ID;
1034     my ($msg, $msg_hash, $module);
1035     my $error = 0;
1036     my $answer_l;
1037     my ($answer_header, @answer_target_l, $answer_source);
1038     my $client_answer = "";
1040     daemon_log("", 5); 
1041     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1042     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1044     ####################
1045     # check incoming msg
1046     # msg is from a new client or gosa
1047     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1048     # msg is from a gosa-si-server or gosa-si-bus
1049     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1050         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1051     }
1052     # msg is from a gosa-si-client
1053     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1054         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1055     }
1056     # an error occurred
1057     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1058         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1059         # could not understand a msg from its server the client cause a re-registering process
1060         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1061         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1062         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1063         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1064             my $host_name = $hit->{'hostname'};
1065             my $host_key = $hit->{'hostkey'};
1066             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1067             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1068             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1069         }
1070         $error++;
1071     }
1073     ######################
1074     # process incoming msg
1075     if( $error == 0) {
1076         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1077                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1078         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1079         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1081         if ( 0 < @{$answer_l} ) {
1082             my $answer_str = join("\n", @{$answer_l});
1083             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1084         }
1085     }
1086     if( !$answer_l ) { $error++ };
1088     ########
1089     # answer
1090     if( $error == 0 ) {
1092         foreach my $answer ( @{$answer_l} ) {
1093             # for each answer in answer list
1094             
1095             # check outgoing msg to xml validity
1096             my $answer_hash = &check_outgoing_xml_validity($answer);
1097             if( not defined $answer_hash ) {
1098                 next;
1099             }
1100             
1101             $answer_header = @{$answer_hash->{'header'}}[0];
1102             @answer_target_l = @{$answer_hash->{'target'}};
1103             $answer_source = @{$answer_hash->{'source'}}[0];
1105             # deliver msg to all targets 
1106             foreach my $answer_target ( @answer_target_l ) {
1108                 # targets of msg are all gosa-si-clients in known_clients_db
1109                 if( $answer_target eq "*" ) {
1110                     # answer is for all clients
1111                     my $sql_statement= "SELECT * FROM known_clients";
1112                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1113                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1114                         my $host_name = $hit->{hostname};
1115                         my $host_key = $hit->{hostkey};
1116                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1117                         &update_jobdb_status_for_send_msgs($answer, $error);
1118                     }
1119                 }
1121                 # targets of msg are all gosa-si-server in known_server_db
1122                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1123                     # answer is for all server in known_server
1124                     my $sql_statement= "SELECT * FROM known_server";
1125                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1126                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1127                         my $host_name = $hit->{hostname};
1128                         my $host_key = $hit->{hostkey};
1129                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1130                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1131                         &update_jobdb_status_for_send_msgs($answer, $error);
1132                     }
1133                 }
1135                 # target of msg is GOsa
1136                                 elsif( $answer_target eq "GOSA" ) {
1137                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1138                                         my $add_on = "";
1139                     if( defined $session_id ) {
1140                         $add_on = ".session_id=$session_id";
1141                     }
1142                     # answer is for GOSA and has to returned to connected client
1143                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1144                     $client_answer = $gosa_answer.$add_on;
1145                 }
1147                 # target of msg is job queue at this host
1148                 elsif( $answer_target eq "JOBDB") {
1149                     $answer =~ /<header>(\S+)<\/header>/;   
1150                     my $header;
1151                     if( defined $1 ) { $header = $1; }
1152                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1153                     &update_jobdb_status_for_send_msgs($answer, $error);
1154                 }
1156                 # target of msg is a mac address
1157                 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 ) {
1158                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1159                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1160                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1161                     my $found_ip_flag = 0;
1162                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1163                         my $host_name = $hit->{hostname};
1164                         my $host_key = $hit->{hostkey};
1165                         $answer =~ s/$answer_target/$host_name/g;
1166                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1167                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1168                         &update_jobdb_status_for_send_msgs($answer, $error);
1169                         $found_ip_flag++ ;
1170                     }   
1171                     if( $found_ip_flag == 0) {
1172                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1173                         if( $bus_activ eq "true" ) { 
1174                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1175                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1176                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1177                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1178                                 my $bus_address = $hit->{hostname};
1179                                 my $bus_key = $hit->{hostkey};
1180                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1181                                 &update_jobdb_status_for_send_msgs($answer, $error);
1182                                 last;
1183                             }
1184                         }
1186                     }
1188                 #  answer is for one specific host   
1189                 } else {
1190                     # get encrypt_key
1191                     my $encrypt_key = &get_encrypt_key($answer_target);
1192                     if( not defined $encrypt_key ) {
1193                         # unknown target, forward msg to bus
1194                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1195                         if( $bus_activ eq "true" ) { 
1196                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1197                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1198                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1199                             my $res_length = keys( %{$query_res} );
1200                             if( $res_length == 0 ){
1201                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1202                                         "no bus found in known_server", 3);
1203                             }
1204                             else {
1205                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1206                                     my $bus_key = $hit->{hostkey};
1207                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1208                                     &update_jobdb_status_for_send_msgs($answer, $error);
1209                                 }
1210                             }
1211                         }
1212                         next;
1213                     }
1214                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1215                     &update_jobdb_status_for_send_msgs($answer, $error);
1216                 }
1217             }
1218         }
1219     }
1221     my $filter = POE::Filter::Reference->new();
1222     my %result = ( 
1223             status => "seems ok to me",
1224             answer => $client_answer,
1225             );
1227     my $output = $filter->put( [ \%result ] );
1228     print @$output;
1234 sub trigger_db_loop {
1235         my ($kernel) = @_ ;
1236         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1237         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1240 sub watch_for_done_jobs {
1241     my ($kernel,$heap) = @_[KERNEL, HEAP];
1243     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1244         " WHERE status='done'";
1245         my $res = $job_db->select_dbentry( $sql_statement );
1247     while( my ($id, $hit) = each %{$res} ) {
1248         my $jobdb_id = $hit->{id};
1249         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1250         my $res = $job_db->del_dbentry($sql_statement);
1251     }
1253     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1256 sub watch_for_new_jobs {
1257         my ($kernel,$heap) = @_[KERNEL, HEAP];
1259         # check gosa job queue for jobs with executable timestamp
1260         my $timestamp = &get_time();
1261         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1262         my $res = $job_db->exec_statement( $sql_statement );
1264         # Merge all new jobs that would do the same actions
1265         my @drops;
1266         my $hits;
1267         foreach my $hit (reverse @{$res} ) {
1268                 my $macaddress= lc @{$hit}[8];
1269                 my $headertag= @{$hit}[5];
1270                 if(defined($hits->{$macaddress}->{$headertag})) {
1271                         push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1272                 }
1273                 $hits->{$macaddress}->{$headertag}= $hit;
1274         }
1276         # Delete new jobs with a matching job in state 'processing'
1277         foreach my $macaddress (keys %{$hits}) {
1278                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1279                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1280                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1281                         my $res = $job_db->exec_statement( $sql_statement );
1282                         foreach my $hit (@{$res}) {
1283                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1284                         }
1285                 }
1286         }
1288         # Commit deletion
1289         $job_db->exec_statementlist(\@drops);
1291         # Look for new jobs that could be executed
1292         foreach my $macaddress (keys %{$hits}) {
1294                 # Look if there is an executing job
1295                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1296                 my $res = $job_db->exec_statement( $sql_statement );
1298                 # Skip new jobs for host if there is a processing job
1299                 if(defined($res) and defined @{$res}[0]) {
1300                         next;
1301                 }
1303                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1304                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1305                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1307                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1308                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1309                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1311                         # expect macaddress is unique!!!!!!
1312                         my $target = $res_hash->{1}->{hostname};
1314                         # change header
1315                         $job_msg =~ s/<header>job_/<header>gosa_/;
1317                         # add sqlite_id
1318                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1320                         $job_msg =~ /<header>(\S+)<\/header>/;
1321                         my $header = $1 ;
1322                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1324                         # update status in job queue to 'processing'
1325                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1326                         my $res = $job_db->update_dbentry($sql_statement);
1328                         # We don't want parallel processing
1329                         last;
1330                 }
1331         }
1333         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1337 sub get_ldap_handle {
1338         my ($session_id) = @_;
1339         my $heap;
1340         my $ldap_handle;
1342         if (not defined $session_id ) { $session_id = 0 };
1344         if ($session_id == 0) {
1345                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1346                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1347                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1349         } else {
1350                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1351                 if( defined $session_reference ) {
1352                         $heap = $session_reference->get_heap();
1353                 }
1355                 if (not defined $heap) {
1356                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1357                         return;
1358                 }
1360                 # TODO: This "if" is nonsense, because it doesn't prove that the
1361                 #       used handle is still valid - or if we've to reconnect...
1362                 #if (not exists $heap->{ldap_handle}) {
1363                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1364                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1365                         $heap->{ldap_handle} = $ldap_handle;
1366                 #}
1367         }
1368         return $ldap_handle;
1372 sub change_fai_state {
1373     my ($st, $targets, $session_id) = @_;
1374     $session_id = 0 if not defined $session_id;
1375     # Set FAI state to localboot
1376     my %mapActions= (
1377         reboot    => '',
1378         update    => 'softupdate',
1379         localboot => 'localboot',
1380         reinstall => 'install',
1381         rescan    => '',
1382         wake      => '',
1383         memcheck  => 'memcheck',
1384         sysinfo   => 'sysinfo',
1385         install   => 'install',
1386     );
1388     # Return if this is unknown
1389     if (!exists $mapActions{ $st }){
1390         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1391       return;
1392     }
1394     my $state= $mapActions{ $st };
1396     my $ldap_handle = &get_ldap_handle($session_id);
1397     if( defined($ldap_handle) ) {
1399       # Build search filter for hosts
1400         my $search= "(&(objectClass=GOhard)";
1401         foreach (@{$targets}){
1402             $search.= "(macAddress=$_)";
1403         }
1404         $search.= ")";
1406       # If there's any host inside of the search string, procress them
1407         if (!($search =~ /macAddress/)){
1408             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1409             return;
1410         }
1412       # Perform search for Unit Tag
1413       my $mesg = $ldap_handle->search(
1414           base   => $ldap_base,
1415           scope  => 'sub',
1416           attrs  => ['dn', 'FAIstate', 'objectClass'],
1417           filter => "$search"
1418           );
1420           if ($mesg->count) {
1421                   my @entries = $mesg->entries;
1422                   foreach my $entry (@entries) {
1423                           # Only modify entry if it is not set to '$state'
1424                           if ($entry->get_value("FAIstate") ne "$state"){
1425                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1426                                   my $result;
1427                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1428                                   if (exists $tmp{'FAIobject'}){
1429                                           if ($state eq ''){
1430                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1431                                                           delete => [ FAIstate => [] ] ]);
1432                                           } else {
1433                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1434                                                           replace => [ FAIstate => $state ] ]);
1435                                           }
1436                                   } elsif ($state ne ''){
1437                                           $result= $ldap_handle->modify($entry->dn, changes => [
1438                                                   add     => [ objectClass => 'FAIobject' ],
1439                                                   add     => [ FAIstate => $state ] ]);
1440                                   }
1442                                   # Errors?
1443                                   if ($result->code){
1444                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1445                                   }
1446                           } else {
1447                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1448                           }  
1449                   }
1450           }
1451     # if no ldap handle defined
1452     } else {
1453         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1454     }
1459 sub change_goto_state {
1460     my ($st, $targets, $session_id) = @_;
1461     $session_id = 0  if not defined $session_id;
1463     # Switch on or off?
1464     my $state= $st eq 'active' ? 'active': 'locked';
1466     my $ldap_handle = &get_ldap_handle($session_id);
1467     if( defined($ldap_handle) ) {
1469       # Build search filter for hosts
1470       my $search= "(&(objectClass=GOhard)";
1471       foreach (@{$targets}){
1472         $search.= "(macAddress=$_)";
1473       }
1474       $search.= ")";
1476       # If there's any host inside of the search string, procress them
1477       if (!($search =~ /macAddress/)){
1478         return;
1479       }
1481       # Perform search for Unit Tag
1482       my $mesg = $ldap_handle->search(
1483           base   => $ldap_base,
1484           scope  => 'sub',
1485           attrs  => ['dn', 'gotoMode'],
1486           filter => "$search"
1487           );
1489       if ($mesg->count) {
1490         my @entries = $mesg->entries;
1491         foreach my $entry (@entries) {
1493           # Only modify entry if it is not set to '$state'
1494           if ($entry->get_value("gotoMode") ne $state){
1496             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1497             my $result;
1498             $result= $ldap_handle->modify($entry->dn, changes => [
1499                                                 replace => [ gotoMode => $state ] ]);
1501             # Errors?
1502             if ($result->code){
1503               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1504             }
1506           }
1507         }
1508       }
1510     }
1514 sub create_fai_server_db {
1515     my ($table_name, $kernel) = @_;
1516         my $result;
1517     my $ldap_handle = &get_ldap_handle();
1518         if(defined($ldap_handle)) {
1519                 daemon_log("INFO: create_fai_server_db: start", 5);
1520                 my $mesg= $ldap_handle->search(
1521                         base   => $ldap_base,
1522                         scope  => 'sub',
1523                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1524                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1525                 );
1526                 if($mesg->{'resultCode'} == 0 &&
1527                    $mesg->count != 0) {
1528                    foreach my $entry (@{$mesg->{entries}}) {
1529                            if($entry->exists('FAIrepository')) {
1530                                    # Add an entry for each Repository configured for server
1531                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1532                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1533                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1534                                                    $result= $fai_server_db->add_dbentry( { 
1535                                                                    table => $table_name,
1536                                                                    primkey => ['server', 'release', 'tag'],
1537                                                                    server => $tmp_url,
1538                                                                    release => $tmp_release,
1539                                                                    sections => $tmp_sections,
1540                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1541                                                            } );
1542                                            }
1543                                    }
1544                            }
1545                    }
1546                 daemon_log("INFO: create_fai_server_db: finished", 5);
1548                 # TODO: Find a way to post the 'create_packages_list_db' event
1549                 &create_packages_list_db($ldap_handle);
1550         }       
1551     
1552     $ldap_handle->disconnect;
1553         return $result;
1556 sub run_create_fai_server_db {
1557     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1558     my $task = POE::Wheel::Run->new(
1559             Program => sub { &create_fai_server_db($table_name,$kernel) },
1560             StdoutEvent  => "session_run_result",
1561             StderrEvent  => "session_run_debug",
1562             CloseEvent   => "session_run_done",
1563             );
1565     $heap->{task}->{ $task->ID } = $task;
1566     return;
1570 sub create_fai_release_db {
1571         my ($table_name) = @_;
1572         my $result;
1574     my $ldap_handle = &get_ldap_handle();
1575         if(defined($ldap_handle)) {
1576                 daemon_log("INFO: create_fai_release_db: start",5);
1577                 my $mesg= $ldap_handle->search(
1578                         base   => $ldap_base,
1579                         scope  => 'sub',
1580                         attrs  => [],
1581                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1582                 );
1583                 if($mesg->{'resultCode'} == 0 &&
1584                         $mesg->count != 0) {
1585                         # Walk through all possible FAI container ou's
1586                         my @sql_list;
1587                         my $timestamp= &get_time();
1588                         foreach my $ou (@{$mesg->{entries}}) {
1589                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1590                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1591                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1592                                         if(@tmp_array) {
1593                                                 foreach my $entry (@tmp_array) {
1594                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1595                                                                 my $sql= 
1596                                                                 "INSERT INTO $table_name "
1597                                                                 ."(timestamp, release, class, type, state) VALUES ("
1598                                                                 .$timestamp.","
1599                                                                 ."'".$entry->{'release'}."',"
1600                                                                 ."'".$entry->{'class'}."',"
1601                                                                 ."'".$entry->{'type'}."',"
1602                                                                 ."'".$entry->{'state'}."')";
1603                                                                 push @sql_list, $sql;
1604                                                         }
1605                                                 }
1606                                         }
1607                                 }
1608                         }
1609                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1610                         if(@sql_list) {
1611                                 unshift @sql_list, "DELETE FROM $table_name";
1612                                 $fai_server_db->exec_statementlist(\@sql_list);
1613                         }
1614                         daemon_log("DEBUG: Done with inserting",6);
1615                 }
1616                 daemon_log("INFO: create_fai_release_db: finished",5);
1617         }
1618     $ldap_handle->disconnect;
1619         return $result;
1621 sub run_create_fai_release_db {
1622     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1623     my $task = POE::Wheel::Run->new(
1624             Program => sub { &create_fai_release_db($table_name) },
1625             StdoutEvent  => "session_run_result",
1626             StderrEvent  => "session_run_debug",
1627             CloseEvent   => "session_run_done",
1628             );
1630     $heap->{task}->{ $task->ID } = $task;
1631     return;
1634 sub get_fai_types {
1635         my $tmp_classes = shift || return undef;
1636         my @result;
1638         foreach my $type(keys %{$tmp_classes}) {
1639                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1640                         my $entry = {
1641                                 type => $type,
1642                                 state => $tmp_classes->{$type}[0],
1643                         };
1644                         push @result, $entry;
1645                 }
1646         }
1648         return @result;
1651 sub get_fai_state {
1652         my $result = "";
1653         my $tmp_classes = shift || return $result;
1655         foreach my $type(keys %{$tmp_classes}) {
1656                 if(defined($tmp_classes->{$type}[0])) {
1657                         $result = $tmp_classes->{$type}[0];
1658                         
1659                 # State is equal for all types in class
1660                         last;
1661                 }
1662         }
1664         return $result;
1667 sub resolve_fai_classes {
1668         my ($fai_base, $ldap_handle) = @_;
1669         my $result;
1670         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1671         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1672         my $fai_classes;
1674         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1675         my $mesg= $ldap_handle->search(
1676                 base   => $fai_base,
1677                 scope  => 'sub',
1678                 attrs  => ['cn','objectClass','FAIstate'],
1679                 filter => $fai_filter,
1680         );
1681         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1683         if($mesg->{'resultCode'} == 0 &&
1684                 $mesg->count != 0) {
1685                 foreach my $entry (@{$mesg->{entries}}) {
1686                         if($entry->exists('cn')) {
1687                                 my $tmp_dn= $entry->dn();
1689                                 # Skip classname and ou dn parts for class
1690                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1692                                 # Skip classes without releases
1693                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1694                                         next;
1695                                 }
1697                                 my $tmp_cn= $entry->get_value('cn');
1698                                 my $tmp_state= $entry->get_value('FAIstate');
1700                                 my $tmp_type;
1701                                 # Get FAI type
1702                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1703                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1704                                                 $tmp_type= $oclass;
1705                                                 last;
1706                                         }
1707                                 }
1709                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1710                                         # A Subrelease
1711                                         my @sub_releases = split(/,/, $tmp_release);
1713                                         # Walk through subreleases and build hash tree
1714                                         my $hash;
1715                                         while(my $tmp_sub_release = pop @sub_releases) {
1716                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1717                                         }
1718                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1719                                 } else {
1720                                         # A branch, no subrelease
1721                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1722                                 }
1723                         } elsif (!$entry->exists('cn')) {
1724                                 my $tmp_dn= $entry->dn();
1725                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1727                                 # Skip classes without releases
1728                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1729                                         next;
1730                                 }
1732                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1733                                         # A Subrelease
1734                                         my @sub_releases= split(/,/, $tmp_release);
1736                                         # Walk through subreleases and build hash tree
1737                                         my $hash;
1738                                         while(my $tmp_sub_release = pop @sub_releases) {
1739                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1740                                         }
1741                                         # Remove the last two characters
1742                                         chop($hash);
1743                                         chop($hash);
1745                                         eval('$fai_classes->'.$hash.'= {}');
1746                                 } else {
1747                                         # A branch, no subrelease
1748                                         if(!exists($fai_classes->{$tmp_release})) {
1749                                                 $fai_classes->{$tmp_release} = {};
1750                                         }
1751                                 }
1752                         }
1753                 }
1755                 # The hash is complete, now we can honor the copy-on-write based missing entries
1756                 foreach my $release (keys %$fai_classes) {
1757                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1758                 }
1759         }
1760         return $result;
1763 sub apply_fai_inheritance {
1764        my $fai_classes = shift || return {};
1765        my $tmp_classes;
1767        # Get the classes from the branch
1768        foreach my $class (keys %{$fai_classes}) {
1769                # Skip subreleases
1770                if($class =~ /^ou=.*$/) {
1771                        next;
1772                } else {
1773                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1774                }
1775        }
1777        # Apply to each subrelease
1778        foreach my $subrelease (keys %{$fai_classes}) {
1779                if($subrelease =~ /ou=/) {
1780                        foreach my $tmp_class (keys %{$tmp_classes}) {
1781                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1782                                        $fai_classes->{$subrelease}->{$tmp_class} =
1783                                        deep_copy($tmp_classes->{$tmp_class});
1784                                } else {
1785                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1786                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1787                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1788                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1789                                                }
1790                                        }
1791                                }
1792                        }
1793                }
1794        }
1796        # Find subreleases in deeper levels
1797        foreach my $subrelease (keys %{$fai_classes}) {
1798                if($subrelease =~ /ou=/) {
1799                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1800                                if($subsubrelease =~ /ou=/) {
1801                                        apply_fai_inheritance($fai_classes->{$subrelease});
1802                                }
1803                        }
1804                }
1805        }
1807        return $fai_classes;
1810 sub get_fai_release_entries {
1811         my $tmp_classes = shift || return;
1812         my $parent = shift || "";
1813         my @result = shift || ();
1815         foreach my $entry (keys %{$tmp_classes}) {
1816                 if(defined($entry)) {
1817                         if($entry =~ /^ou=.*$/) {
1818                                 my $release_name = $entry;
1819                                 $release_name =~ s/ou=//g;
1820                                 if(length($parent)>0) {
1821                                         $release_name = $parent."/".$release_name;
1822                                 }
1823                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1824                                 foreach my $bufentry(@bufentries) {
1825                                         push @result, $bufentry;
1826                                 }
1827                         } else {
1828                                 my @types = get_fai_types($tmp_classes->{$entry});
1829                                 foreach my $type (@types) {
1830                                         push @result, 
1831                                         {
1832                                                 'class' => $entry,
1833                                                 'type' => $type->{'type'},
1834                                                 'release' => $parent,
1835                                                 'state' => $type->{'state'},
1836                                         };
1837                                 }
1838                         }
1839                 }
1840         }
1842         return @result;
1845 sub deep_copy {
1846         my $this = shift;
1847         if (not ref $this) {
1848                 $this;
1849         } elsif (ref $this eq "ARRAY") {
1850                 [map deep_copy($_), @$this];
1851         } elsif (ref $this eq "HASH") {
1852                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1853         } else { die "what type is $_?" }
1857 sub session_run_result {
1858     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1859     $kernel->sig(CHLD => "child_reap");
1862 sub session_run_debug {
1863     my $result = $_[ARG0];
1864     print STDERR "$result\n";
1867 sub session_run_done {
1868     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1869     delete $heap->{task}->{$task_id};
1872 sub create_sources_list {
1873     my ($ldap_handle) = @_;
1874         my $result="/tmp/gosa_si_tmp_sources_list";
1876         # Remove old file
1877         if(stat($result)) {
1878                 unlink($result);
1879         }
1881         my $fh;
1882         open($fh, ">$result") or return undef;
1883         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1884                 my $mesg=$ldap_handle->search(
1885                                 base    => $ldap_server_dn,
1886                                 scope   => 'base',
1887                                 attrs   => 'FAIrepository',
1888                                 filter  => 'objectClass=FAIrepositoryServer'
1889                                 );
1890                 if($mesg->count) {
1891                         foreach my $entry(@{$mesg->{'entries'}}) {
1892                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
1893                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
1894                                         my $line = "deb $server $release";
1895                                         $sections =~ s/,/ /g;
1896                                         $line.= " $sections";
1897                                         print $fh $line."\n";
1898                                 }
1899                         }
1900                 }
1901         }
1902         close($fh);
1904         return $result;
1907 sub create_packages_list_db {
1908     my ($ldap_handle, $sources_file) = @_ ;
1910         if (not defined $ldap_handle) { 
1911                 $ldap_handle= &get_ldap_handle();
1913                 if (not defined $ldap_handle) {
1914                         daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1915                         return;
1916                 }
1917         }
1919     if (not defined $sources_file) { 
1920         $sources_file = &create_sources_list($ldap_handle);
1921     }
1923     my $line;
1924     daemon_log("INFO: create_packages_list_db: start", 5); 
1926     open(CONFIG, "<$sources_file") or do {
1927         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1928         return;
1929     };
1930     
1931     # Read lines
1932     while ($line = <CONFIG>){
1933         # Unify
1934         chop($line);
1935         $line =~ s/^\s+//;
1936         $line =~ s/^\s+/ /;
1938         # Strip comments
1939         $line =~ s/#.*$//g;
1941         # Skip empty lines
1942         if ($line =~ /^\s*$/){
1943             next;
1944         }
1946         # Interpret deb line
1947         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1948             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1949             my $section;
1950             foreach $section (split(' ', $sections)){
1951                 &parse_package_info( $baseurl, $dist, $section );
1952             }
1953         }
1954     }
1956     close (CONFIG);
1958     daemon_log("INFO: create_packages_list_db: finished", 5); 
1959     return;
1962 sub run_create_packages_list_db {
1963     my ($session, $heap) = @_[SESSION, HEAP];
1964     my $task = POE::Wheel::Run->new(
1965             Program => sub {&create_packages_list_db},
1966             StdoutEvent  => "session_run_result",
1967             StderrEvent  => "session_run_debug",
1968             CloseEvent   => "session_run_done",
1969             );
1970     $heap->{task}->{ $task->ID } = $task;
1973 sub parse_package_info {
1974   my ($baseurl, $dist, $section)= @_;
1975   my ($package);
1977   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1978   $repo_dirs{ "${repo_path}/pool" } = 1;
1980   foreach $package ("Packages.gz"){
1981     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1982     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1983     parse_package( "$outdir/$dist/$section", $dist, $path );
1984   }
1985   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1988 sub get_package {
1989   my ($url, $dest)= @_;
1991   my $tpath = dirname($dest);
1992   -d "$tpath" || mkpath "$tpath";
1994   # This is ugly, but I've no time to take a look at "how it works in perl"
1995   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1996       system("gunzip -cd '$dest' > '$dest.in'");
1997       unlink($dest);
1998   } else {
1999       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2000   }
2001   return 0;
2004 sub parse_package {
2005     my ($path, $dist, $srv_path)= @_;
2006     my ($package, $version, $section, $description);
2007     my @sql_list;
2008     my $PACKAGES;
2010     if(not stat("$path.in")) {
2011         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2012         return;
2013     }
2015     open($PACKAGES, "<$path.in");
2016         if(not defined($PACKAGES)) {
2017         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
2018         return;
2019     }
2021     # Read lines
2022     while (<$PACKAGES>){
2023         my $line = $_;
2024         # Unify
2025         chop($line);
2027         # Use empty lines as a trigger
2028         if ($line =~ /^\s*$/){
2029             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2030             push(@sql_list, $sql);
2031             $package = "none";
2032             $version = "none";
2033             $section = "none";
2034             $description = "none"; 
2035             next;
2036         }
2038         # Trigger for package name
2039         if ($line =~ /^Package:\s/){
2040             ($package)= ($line =~ /^Package: (.*)$/);
2041             next;
2042         }
2044         # Trigger for version
2045         if ($line =~ /^Version:\s/){
2046             ($version)= ($line =~ /^Version: (.*)$/);
2047             next;
2048         }
2050         # Trigger for description
2051         if ($line =~ /^Description:\s/){
2052             ($description)= ($line =~ /^Description: (.*)$/);
2053             next;
2054         }
2056         # Trigger for section
2057         if ($line =~ /^Section:\s/){
2058             ($section)= ($line =~ /^Section: (.*)$/);
2059             next;
2060         }
2062         # Trigger for filename
2063         if ($line =~ /^Filename:\s/){
2064                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2065                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2066                 next;
2067         }
2068     }
2070     close( $PACKAGES );
2071     unlink( "$path.in" );
2072     
2073     $packages_list_db->exec_statementlist(\@sql_list);
2076 sub store_fileinfo {
2077   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2079   my %fileinfo = (
2080     'package' => $package,
2081     'dist' => $dist,
2082     'version' => $vers,
2083   );
2085   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2088 sub cleanup_and_extract {
2089   my $fileinfo = $repo_files{ $File::Find::name };
2091   if( defined $fileinfo ) {
2093     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2094     my $sql;
2095     my $package = $fileinfo->{ 'package' };
2096     my $newver = $fileinfo->{ 'version' };
2098     mkpath($dir);
2099     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2101     if( -f "$dir/DEBIAN/templates" ) {
2103       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2105       my $tmpl= "";
2106       {
2107           local $/=undef;
2108           open FILE, "$dir/DEBIAN/templates";
2109           $tmpl = &encode_base64(<FILE>);
2110           close FILE;
2111       }
2112       rmtree("$dir/DEBIAN/templates");
2114       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2116     } else {
2117       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2118     }
2120     my $res= $main::packages_list_db->update_dbentry($sql);
2121   }
2125 #==== MAIN = main ==============================================================
2126 #  parse commandline options
2127 Getopt::Long::Configure( "bundling" );
2128 GetOptions("h|help" => \&usage,
2129         "c|config=s" => \$cfg_file,
2130         "f|foreground" => \$foreground,
2131         "v|verbose+" => \$verbose,
2132         "no-bus+" => \$no_bus,
2133         "no-arp+" => \$no_arp,
2134            );
2136 #  read and set config parameters
2137 &check_cmdline_param ;
2138 &read_configfile;
2139 &check_pid;
2141 $SIG{CHLD} = 'IGNORE';
2143 # forward error messages to logfile
2144 if( ! $foreground ) {
2145   open( STDIN,  '+>/dev/null' );
2146   open( STDOUT, '+>&STDIN'    );
2147   open( STDERR, '+>&STDIN'    );
2150 # Just fork, if we are not in foreground mode
2151 if( ! $foreground ) { 
2152     chdir '/'                 or die "Can't chdir to /: $!";
2153     $pid = fork;
2154     setsid                    or die "Can't start a new session: $!";
2155     umask 0;
2156 } else { 
2157     $pid = $$; 
2160 # Do something useful - put our PID into the pid_file
2161 if( 0 != $pid ) {
2162     open( LOCK_FILE, ">$pid_file" );
2163     print LOCK_FILE "$pid\n";
2164     close( LOCK_FILE );
2165     if( !$foreground ) { 
2166         exit( 0 ) 
2167     };
2170 daemon_log(" ", 1);
2171 daemon_log("$0 started!", 1);
2173 if ($no_bus > 0) {
2174     $bus_activ = "false"
2177 # connect to gosa-si job queue
2178 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2179 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2181 # connect to known_clients_db
2182 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2183 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2185 # connect to known_server_db
2186 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2187 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2189 # connect to login_usr_db
2190 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2191 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2193 # connect to fai_server_db and fai_release_db
2194 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2195 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2196 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2198 # connect to packages_list_db
2199 unlink($packages_list_file_name);
2200 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2201 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2203 # connect to messaging_db
2204 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2205 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2208 # create xml object used for en/decrypting
2209 $xml = new XML::Simple();
2211 # create socket for incoming xml messages
2213 POE::Component::Server::TCP->new(
2214         Port => $server_port,
2215         ClientInput => sub {
2216         my ($kernel, $input) = @_[KERNEL, ARG0];
2217         push(@tasks, $input);
2218         $kernel->yield("next_task");
2219         },
2220     InlineStates => {
2221         next_task => \&next_task,
2222         task_result => \&handle_task_result,
2223         task_done   => \&handle_task_done,
2224         task_debug  => \&handle_task_debug,
2225         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2226     }
2227 );
2229 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2231 # create session for repeatedly checking the job queue for jobs
2232 POE::Session->create(
2233         inline_states => {
2234                 _start => \&_start,
2235                 sig_handler => \&sig_handler,
2236                 watch_for_new_jobs => \&watch_for_new_jobs,
2237         watch_for_done_jobs => \&watch_for_done_jobs,
2238         create_packages_list_db => \&run_create_packages_list_db,
2239         create_fai_server_db => \&run_create_fai_server_db,
2240         create_fai_release_db => \&run_create_fai_release_db,
2241         session_run_result => \&session_run_result,
2242         session_run_debug => \&session_run_debug,
2243         session_run_done => \&session_run_done,
2244         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2245         }
2246 );
2249 # import all modules
2250 &import_modules;
2252 # check wether all modules are gosa-si valid passwd check
2254 POE::Kernel->run();
2255 exit;