Code

3522b8fb63d4e73773a86d722f216cfe8ef6b5f5
[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) && ( length($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("killall gosa-si-server");
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         #################################
969         # out commented, just for testing
970         #$kernel->yield('create_fai_server_db', $fai_server_tn );
971         #$kernel->yield('create_fai_release_db', $fai_release_tn );
972         $kernel->sig(USR1 => "sig_handler");
975 sub sig_handler {
976         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
977         daemon_log("0 INFO got signal '$signal'", 1); 
978         $kernel->sig_handled();
979         return;
982 sub next_task {
983     my ($session, $heap) = @_[SESSION, HEAP];
985     while ( keys( %{ $heap->{task} } ) < $max_children ) {
986         my $next_task = shift @tasks;
987         last unless defined $next_task;
989         my $task = POE::Wheel::Run->new(
990                 Program => sub { process_task($session, $heap, $next_task) },
991                 StdioFilter => POE::Filter::Reference->new(),
992                 StdoutEvent  => "task_result",
993                 StderrEvent  => "task_debug",
994                 CloseEvent   => "task_done",
995                );
997         $heap->{task}->{ $task->ID } = $task;
998     }
1001 sub handle_task_result {
1002     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1003     my $client_answer = $result->{'answer'};
1004     if( $client_answer =~ s/session_id=(\d+)$// ) {
1005         my $session_id = $1;
1006         if( defined $session_id ) {
1007             my $session_reference = $kernel->ID_id_to_session($session_id);
1008             if( defined $session_reference ) {
1009                 $heap = $session_reference->get_heap();
1010             }
1011         }
1013         if(exists $heap->{'client'}) {
1014             $heap->{'client'}->put($client_answer);
1015         }
1016     }
1017     $kernel->sig(CHLD => "child_reap");
1020 sub handle_task_debug {
1021     my $result = $_[ARG0];
1022     print STDERR "$result\n";
1025 sub handle_task_done {
1026     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1027     delete $heap->{task}->{$task_id};
1028     $kernel->yield("next_task");
1031 sub process_task {
1032     no strict "refs";
1033     my ($session, $heap, $input) = @_;
1034     my $session_id = $session->ID;
1035     my ($msg, $msg_hash, $module);
1036     my $error = 0;
1037     my $answer_l;
1038     my ($answer_header, @answer_target_l, $answer_source);
1039     my $client_answer = "";
1041     daemon_log("", 5); 
1042     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1043     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1045     ####################
1046     # check incoming msg
1047     # msg is from a new client or gosa
1048     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1049     # msg is from a gosa-si-server or gosa-si-bus
1050     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1051         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1052     }
1053     # msg is from a gosa-si-client
1054     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1055         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1056     }
1057     # an error occurred
1058     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1059         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1060         # could not understand a msg from its server the client cause a re-registering process
1061         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);
1062         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1063         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1064         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1065             my $host_name = $hit->{'hostname'};
1066             my $host_key = $hit->{'hostkey'};
1067             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1068             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1069             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1070         }
1071         $error++;
1072     }
1074     ######################
1075     # process incoming msg
1076     if( $error == 0) {
1077         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1078                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1079         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1080         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1082         if ( 0 < @{$answer_l} ) {
1083             my $answer_str = join("\n", @{$answer_l});
1084             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1085         }
1086     }
1087     if( !$answer_l ) { $error++ };
1089     ########
1090     # answer
1091     if( $error == 0 ) {
1093         foreach my $answer ( @{$answer_l} ) {
1094             # for each answer in answer list
1095             
1096             # check outgoing msg to xml validity
1097             my $answer_hash = &check_outgoing_xml_validity($answer);
1098             if( not defined $answer_hash ) {
1099                 next;
1100             }
1101             
1102             $answer_header = @{$answer_hash->{'header'}}[0];
1103             @answer_target_l = @{$answer_hash->{'target'}};
1104             $answer_source = @{$answer_hash->{'source'}}[0];
1106             # deliver msg to all targets 
1107             foreach my $answer_target ( @answer_target_l ) {
1109                 # targets of msg are all gosa-si-clients in known_clients_db
1110                 if( $answer_target eq "*" ) {
1111                     # answer is for all clients
1112                     my $sql_statement= "SELECT * FROM known_clients";
1113                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1114                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1115                         my $host_name = $hit->{hostname};
1116                         my $host_key = $hit->{hostkey};
1117                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1118                         &update_jobdb_status_for_send_msgs($answer, $error);
1119                     }
1120                 }
1122                 # targets of msg are all gosa-si-server in known_server_db
1123                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1124                     # answer is for all server in known_server
1125                     my $sql_statement= "SELECT * FROM known_server";
1126                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1127                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1128                         my $host_name = $hit->{hostname};
1129                         my $host_key = $hit->{hostkey};
1130                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1131                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1132                         &update_jobdb_status_for_send_msgs($answer, $error);
1133                     }
1134                 }
1136                 # target of msg is GOsa
1137                                 elsif( $answer_target eq "GOSA" ) {
1138                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1139                                         my $add_on = "";
1140                     if( defined $session_id ) {
1141                         $add_on = ".session_id=$session_id";
1142                     }
1143                     # answer is for GOSA and has to returned to connected client
1144                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1145                     $client_answer = $gosa_answer.$add_on;
1146                 }
1148                 # target of msg is job queue at this host
1149                 elsif( $answer_target eq "JOBDB") {
1150                     $answer =~ /<header>(\S+)<\/header>/;   
1151                     my $header;
1152                     if( defined $1 ) { $header = $1; }
1153                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1154                     &update_jobdb_status_for_send_msgs($answer, $error);
1155                 }
1157                 # target of msg is a mac address
1158                 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 ) {
1159                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1160                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1161                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1162                     my $found_ip_flag = 0;
1163                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1164                         my $host_name = $hit->{hostname};
1165                         my $host_key = $hit->{hostkey};
1166                         $answer =~ s/$answer_target/$host_name/g;
1167                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1168                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1169                         &update_jobdb_status_for_send_msgs($answer, $error);
1170                         $found_ip_flag++ ;
1171                     }   
1172                     if( $found_ip_flag == 0) {
1173                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1174                         if( $bus_activ eq "true" ) { 
1175                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1176                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1177                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1178                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1179                                 my $bus_address = $hit->{hostname};
1180                                 my $bus_key = $hit->{hostkey};
1181                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1182                                 &update_jobdb_status_for_send_msgs($answer, $error);
1183                                 last;
1184                             }
1185                         }
1187                     }
1189                 #  answer is for one specific host   
1190                 } else {
1191                     # get encrypt_key
1192                     my $encrypt_key = &get_encrypt_key($answer_target);
1193                     if( not defined $encrypt_key ) {
1194                         # unknown target, forward msg to bus
1195                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1196                         if( $bus_activ eq "true" ) { 
1197                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1198                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1199                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1200                             my $res_length = keys( %{$query_res} );
1201                             if( $res_length == 0 ){
1202                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1203                                         "no bus found in known_server", 3);
1204                             }
1205                             else {
1206                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1207                                     my $bus_key = $hit->{hostkey};
1208                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1209                                     &update_jobdb_status_for_send_msgs($answer, $error);
1210                                 }
1211                             }
1212                         }
1213                         next;
1214                     }
1215                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1216                     &update_jobdb_status_for_send_msgs($answer, $error);
1217                 }
1218             }
1219         }
1220     }
1222     my $filter = POE::Filter::Reference->new();
1223     my %result = ( 
1224             status => "seems ok to me",
1225             answer => $client_answer,
1226             );
1228     my $output = $filter->put( [ \%result ] );
1229     print @$output;
1235 sub trigger_db_loop {
1236         my ($kernel) = @_ ;
1237         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1238     $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1241 sub watch_for_done_jobs {
1242     my ($kernel,$heap) = @_[KERNEL, HEAP];
1244     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1245         " WHERE status='done'";
1246         my $res = $job_db->select_dbentry( $sql_statement );
1248     while( my ($id, $hit) = each %{$res} ) {
1249         my $jobdb_id = $hit->{id};
1250         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1251         my $res = $job_db->del_dbentry($sql_statement);
1252     }
1254     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1257 sub watch_for_new_jobs {
1258         my ($kernel,$heap) = @_[KERNEL, HEAP];
1260         # check gosa job queue for jobs with executable timestamp
1261     my $timestamp = &get_time();
1262     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1263         " WHERE status='waiting' AND timestamp<'$timestamp'";
1264         my $res = $job_db->select_dbentry( $sql_statement );
1266         while( my ($id, $hit) = each %{$res} ) {         
1267                 my $jobdb_id = $hit->{id};
1268                 my $macaddress = $hit->{'macaddress'};
1269         my $job_msg = $hit->{'xmlmessage'};
1270         daemon_log("J DEBUG: its time to execute $job_msg", 7); 
1271         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1272                 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1273                 # expect macaddress is unique!!!!!!
1274                 my $target = $res_hash->{1}->{hostname};
1276                 # change header
1277         $job_msg =~ s/<header>job_/<header>gosa_/;
1279                 # add sqlite_id 
1280         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1282         $job_msg =~ /<header>(\S+)<\/header>/;
1283         my $header = $1 ;
1284                 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1286         # update status in job queue to 'processing'
1287         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1288         my $res = $job_db->update_dbentry($sql_statement);
1289     }
1291         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1295 sub get_ldap_handle {
1296         my ($session_id) = @_;
1297         my $heap;
1298         my $ldap_handle;
1300     if (not defined $session_id ) { $session_id = 0 };
1302         if ($session_id == 0) {
1303         daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1304         $ldap_handle = Net::LDAP->new( $ldap_uri );
1305                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1307         } else {
1308         my $session_reference = $global_kernel->ID_id_to_session($session_id);
1309         if( defined $session_reference ) {
1310             $heap = $session_reference->get_heap();
1311         }
1313         if (not defined $heap) {
1314             daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1315             return;
1316         }
1318         if (not exists $heap->{ldap_handle}) {
1319             # create new ldap handle
1320             $ldap_handle = Net::LDAP->new( $ldap_uri );
1321                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1322             # add ldap handle to heap
1323             $heap->{ldap_handle} = $ldap_handle;
1324         }
1325     }
1326         return $ldap_handle;
1330 sub change_fai_state {
1331     my ($st, $targets, $session_id) = @_;
1332     $session_id = 0 if not defined $session_id;
1333     # Set FAI state to localboot
1334     my %mapActions= (
1335         reboot    => '',
1336         update    => 'softupdate',
1337         localboot => 'localboot',
1338         reinstall => 'install',
1339         rescan    => '',
1340         wake      => '',
1341         memcheck  => 'memcheck',
1342         sysinfo   => 'sysinfo',
1343         install   => 'install',
1344     );
1346     # Return if this is unknown
1347     if (!exists $mapActions{ $st }){
1348         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1349       return;
1350     }
1352     my $state= $mapActions{ $st };
1354     my $ldap_handle = &get_ldap_handle($session_id);
1355     if( defined($ldap_handle) ) {
1357       # Build search filter for hosts
1358         my $search= "(&(objectClass=GOhard)";
1359         foreach (@{$targets}){
1360             $search.= "(macAddress=$_)";
1361         }
1362         $search.= ")";
1364       # If there's any host inside of the search string, procress them
1365         if (!($search =~ /macAddress/)){
1366             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1367             return;
1368         }
1370       # Perform search for Unit Tag
1371       my $mesg = $ldap_handle->search(
1372           base   => $ldap_base,
1373           scope  => 'sub',
1374           attrs  => ['dn', 'FAIstate', 'objectClass'],
1375           filter => "$search"
1376           );
1378       if ($mesg->count) {
1379         my @entries = $mesg->entries;
1380         foreach my $entry (@entries) {
1381           # Only modify entry if it is not set to '$state'
1382           if ($entry->get_value("FAIstate") ne "$state"){
1383             daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1384             my $result;
1385             my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1386             if (exists $tmp{'FAIobject'}){
1387               if ($state eq ''){
1388                 $result= $ldap_handle->modify($entry->dn, changes => [
1389                             delete => [ FAIstate => [] ] ]);
1390               } else {
1391                 $result= $ldap_handle->modify($entry->dn, changes => [
1392                             replace => [ FAIstate => $state ] ]);
1393               }
1394             } elsif ($state ne ''){
1395               $result= $ldap_handle->modify($entry->dn, changes => [
1396                           add     => [ objectClass => 'FAIobject' ],
1397                           add     => [ FAIstate => $state ] ]);
1398             }
1400             # Errors?
1401             if ($result->code){
1402               daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1403             }
1405           } else {
1406             daemon_log("$session_id DEBUG FAIstate at host '$_' already at state '$st'", 7); 
1407           }  
1408         }
1409       }
1410     # if no ldap handle defined
1411     } else {
1412         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1413     }
1418 sub change_goto_state {
1419     my ($st, $targets, $session_id) = @_;
1420     $session_id = 0  if not defined $session_id;
1422     # Switch on or off?
1423     my $state= $st eq 'active' ? 'active': 'locked';
1425     my $ldap_handle = &get_ldap_handle($session_id);
1426     if( defined($ldap_handle) ) {
1428       # Build search filter for hosts
1429       my $search= "(&(objectClass=GOhard)";
1430       foreach (@{$targets}){
1431         $search.= "(macAddress=$_)";
1432       }
1433       $search.= ")";
1435       # If there's any host inside of the search string, procress them
1436       if (!($search =~ /macAddress/)){
1437         return;
1438       }
1440       # Perform search for Unit Tag
1441       my $mesg = $ldap_handle->search(
1442           base   => $ldap_base,
1443           scope  => 'sub',
1444           attrs  => ['dn', 'gotoMode'],
1445           filter => "$search"
1446           );
1448       if ($mesg->count) {
1449         my @entries = $mesg->entries;
1450         foreach my $entry (@entries) {
1452           # Only modify entry if it is not set to '$state'
1453           if ($entry->get_value("gotoMode") ne $state){
1455             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1456             my $result;
1457             $result= $ldap_handle->modify($entry->dn, changes => [
1458                                                 replace => [ gotoMode => $state ] ]);
1460             # Errors?
1461             if ($result->code){
1462               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1463             }
1465           }
1466         }
1467       }
1469     }
1473 sub create_fai_server_db {
1474     my ($table_name, $kernel) = @_;
1475         my $result;
1476     my $ldap_handle = &get_ldap_handle();
1477         if(defined($ldap_handle)) {
1478                 daemon_log("INFO: create_fai_server_db: start", 5);
1479                 my $mesg= $ldap_handle->search(
1480                         base   => $ldap_base,
1481                         scope  => 'sub',
1482                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1483                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1484                 );
1485                 if($mesg->{'resultCode'} == 0 &&
1486                    $mesg->count != 0) {
1487                    foreach my $entry (@{$mesg->{entries}}) {
1488                            if($entry->exists('FAIrepository')) {
1489                                    # Add an entry for each Repository configured for server
1490                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1491                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1492                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1493                                                    $result= $fai_server_db->add_dbentry( { 
1494                                                                    table => $table_name,
1495                                                                    primkey => ['server', 'release', 'tag'],
1496                                                                    server => $tmp_url,
1497                                                                    release => $tmp_release,
1498                                                                    sections => $tmp_sections,
1499                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1500                                                            } );
1501                                            }
1502                                    }
1503                            }
1504                    }
1505                 daemon_log("INFO: create_fai_server_db: finished", 5);
1507                 # TODO: Find a way to post the 'create_packages_list_db' event
1508                 &create_packages_list_db($ldap_handle);
1509         }       
1510     
1511     $ldap_handle->disconnect;
1512         return $result;
1515 sub run_create_fai_server_db {
1516     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1517     my $task = POE::Wheel::Run->new(
1518             Program => sub { &create_fai_server_db($table_name,$kernel) },
1519             StdoutEvent  => "session_run_result",
1520             StderrEvent  => "session_run_debug",
1521             CloseEvent   => "session_run_done",
1522             );
1524     $heap->{task}->{ $task->ID } = $task;
1525     return;
1529 sub create_fai_release_db {
1530         my ($table_name) = @_;
1531         my $result;
1533     my $ldap_handle = &get_ldap_handle();
1534         if(defined($ldap_handle)) {
1535                 daemon_log("INFO: create_fai_release_db: start",5);
1536                 my $mesg= $ldap_handle->search(
1537                         base   => $ldap_base,
1538                         scope  => 'sub',
1539                         attrs  => [],
1540                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1541                 );
1542                 if($mesg->{'resultCode'} == 0 &&
1543                         $mesg->count != 0) {
1544                         # Walk through all possible FAI container ou's
1545                         my @sql_list;
1546                         my $timestamp= &get_time();
1547                         foreach my $ou (@{$mesg->{entries}}) {
1548                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1549                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1550                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1551                                         if(@tmp_array) {
1552                                                 foreach my $entry (@tmp_array) {
1553                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1554                                                                 my $sql= 
1555                                                                 "INSERT INTO $table_name "
1556                                                                 ."(timestamp, release, class, type, state) VALUES ("
1557                                                                 .$timestamp.","
1558                                                                 ."'".$entry->{'release'}."',"
1559                                                                 ."'".$entry->{'class'}."',"
1560                                                                 ."'".$entry->{'type'}."',"
1561                                                                 ."'".$entry->{'state'}."')";
1562                                                                 push @sql_list, $sql;
1563                                                         }
1564                                                 }
1565                                         }
1566                                 }
1567                         }
1568                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1569                         if(@sql_list) {
1570                                 unshift @sql_list, "DELETE FROM $table_name";
1571                                 $fai_server_db->exec_statementlist(\@sql_list);
1572                         }
1573                         daemon_log("DEBUG: Done with inserting",6);
1574                 }
1575                 daemon_log("INFO: create_fai_release_db: finished",5);
1576         }
1577     $ldap_handle->disconnect;
1578         return $result;
1580 sub run_create_fai_release_db {
1581     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1582     my $task = POE::Wheel::Run->new(
1583             Program => sub { &create_fai_release_db($table_name) },
1584             StdoutEvent  => "session_run_result",
1585             StderrEvent  => "session_run_debug",
1586             CloseEvent   => "session_run_done",
1587             );
1589     $heap->{task}->{ $task->ID } = $task;
1590     return;
1593 sub get_fai_types {
1594         my $tmp_classes = shift || return undef;
1595         my @result;
1597         foreach my $type(keys %{$tmp_classes}) {
1598                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1599                         my $entry = {
1600                                 type => $type,
1601                                 state => $tmp_classes->{$type}[0],
1602                         };
1603                         push @result, $entry;
1604                 }
1605         }
1607         return @result;
1610 sub get_fai_state {
1611         my $result = "";
1612         my $tmp_classes = shift || return $result;
1614         foreach my $type(keys %{$tmp_classes}) {
1615                 if(defined($tmp_classes->{$type}[0])) {
1616                         $result = $tmp_classes->{$type}[0];
1617                         
1618                 # State is equal for all types in class
1619                         last;
1620                 }
1621         }
1623         return $result;
1626 sub resolve_fai_classes {
1627         my ($fai_base, $ldap_handle) = @_;
1628         my $result;
1629         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1630         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1631         my $fai_classes;
1633         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1634         my $mesg= $ldap_handle->search(
1635                 base   => $fai_base,
1636                 scope  => 'sub',
1637                 attrs  => ['cn','objectClass','FAIstate'],
1638                 filter => $fai_filter,
1639         );
1640         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1642         if($mesg->{'resultCode'} == 0 &&
1643                 $mesg->count != 0) {
1644                 foreach my $entry (@{$mesg->{entries}}) {
1645                         if($entry->exists('cn')) {
1646                                 my $tmp_dn= $entry->dn();
1648                                 # Skip classname and ou dn parts for class
1649                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1651                                 # Skip classes without releases
1652                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1653                                         next;
1654                                 }
1656                                 my $tmp_cn= $entry->get_value('cn');
1657                                 my $tmp_state= $entry->get_value('FAIstate');
1659                                 my $tmp_type;
1660                                 # Get FAI type
1661                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1662                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1663                                                 $tmp_type= $oclass;
1664                                                 last;
1665                                         }
1666                                 }
1668                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1669                                         # A Subrelease
1670                                         my @sub_releases = split(/,/, $tmp_release);
1672                                         # Walk through subreleases and build hash tree
1673                                         my $hash;
1674                                         while(my $tmp_sub_release = pop @sub_releases) {
1675                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1676                                         }
1677                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1678                                 } else {
1679                                         # A branch, no subrelease
1680                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1681                                 }
1682                         } elsif (!$entry->exists('cn')) {
1683                                 my $tmp_dn= $entry->dn();
1684                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1686                                 # Skip classes without releases
1687                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1688                                         next;
1689                                 }
1691                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1692                                         # A Subrelease
1693                                         my @sub_releases= split(/,/, $tmp_release);
1695                                         # Walk through subreleases and build hash tree
1696                                         my $hash;
1697                                         while(my $tmp_sub_release = pop @sub_releases) {
1698                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1699                                         }
1700                                         # Remove the last two characters
1701                                         chop($hash);
1702                                         chop($hash);
1704                                         eval('$fai_classes->'.$hash.'= {}');
1705                                 } else {
1706                                         # A branch, no subrelease
1707                                         if(!exists($fai_classes->{$tmp_release})) {
1708                                                 $fai_classes->{$tmp_release} = {};
1709                                         }
1710                                 }
1711                         }
1712                 }
1714                 # The hash is complete, now we can honor the copy-on-write based missing entries
1715                 foreach my $release (keys %$fai_classes) {
1716                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1717                 }
1718         }
1719         return $result;
1722 sub apply_fai_inheritance {
1723        my $fai_classes = shift || return {};
1724        my $tmp_classes;
1726        # Get the classes from the branch
1727        foreach my $class (keys %{$fai_classes}) {
1728                # Skip subreleases
1729                if($class =~ /^ou=.*$/) {
1730                        next;
1731                } else {
1732                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1733                }
1734        }
1736        # Apply to each subrelease
1737        foreach my $subrelease (keys %{$fai_classes}) {
1738                if($subrelease =~ /ou=/) {
1739                        foreach my $tmp_class (keys %{$tmp_classes}) {
1740                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1741                                        $fai_classes->{$subrelease}->{$tmp_class} =
1742                                        deep_copy($tmp_classes->{$tmp_class});
1743                                } else {
1744                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1745                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1746                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1747                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1748                                                }
1749                                        }
1750                                }
1751                        }
1752                }
1753        }
1755        # Find subreleases in deeper levels
1756        foreach my $subrelease (keys %{$fai_classes}) {
1757                if($subrelease =~ /ou=/) {
1758                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1759                                if($subsubrelease =~ /ou=/) {
1760                                        apply_fai_inheritance($fai_classes->{$subrelease});
1761                                }
1762                        }
1763                }
1764        }
1766        return $fai_classes;
1769 sub get_fai_release_entries {
1770         my $tmp_classes = shift || return;
1771         my $parent = shift || "";
1772         my @result = shift || ();
1774         foreach my $entry (keys %{$tmp_classes}) {
1775                 if(defined($entry)) {
1776                         if($entry =~ /^ou=.*$/) {
1777                                 my $release_name = $entry;
1778                                 $release_name =~ s/ou=//g;
1779                                 if(length($parent)>0) {
1780                                         $release_name = $parent."/".$release_name;
1781                                 }
1782                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1783                                 foreach my $bufentry(@bufentries) {
1784                                         push @result, $bufentry;
1785                                 }
1786                         } else {
1787                                 my @types = get_fai_types($tmp_classes->{$entry});
1788                                 foreach my $type (@types) {
1789                                         push @result, 
1790                                         {
1791                                                 'class' => $entry,
1792                                                 'type' => $type->{'type'},
1793                                                 'release' => $parent,
1794                                                 'state' => $type->{'state'},
1795                                         };
1796                                 }
1797                         }
1798                 }
1799         }
1801         return @result;
1804 sub deep_copy {
1805         my $this = shift;
1806         if (not ref $this) {
1807                 $this;
1808         } elsif (ref $this eq "ARRAY") {
1809                 [map deep_copy($_), @$this];
1810         } elsif (ref $this eq "HASH") {
1811                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1812         } else { die "what type is $_?" }
1816 sub session_run_result {
1817     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1818     $kernel->sig(CHLD => "child_reap");
1821 sub session_run_debug {
1822     my $result = $_[ARG0];
1823     print STDERR "$result\n";
1826 sub session_run_done {
1827     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1828     delete $heap->{task}->{$task_id};
1831 sub create_sources_list {
1832     my ($ldap_handle) = @_;
1833         my $result="/tmp/gosa_si_tmp_sources_list";
1835         # Remove old file
1836         if(stat($result)) {
1837                 unlink($result);
1838         }
1840         my $fh;
1841         open($fh, ">$result") or return undef;
1842         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1843                 my $mesg=$ldap_handle->search(
1844                                 base    => $ldap_server_dn,
1845                                 scope   => 'base',
1846                                 attrs   => 'FAIrepository',
1847                                 filter  => 'objectClass=FAIrepositoryServer'
1848                                 );
1849                 if($mesg->count) {
1850                         foreach my $entry(@{$mesg->{'entries'}}) {
1851                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1852                                 my $line = "deb $server $release";
1853                                 $sections =~ s/,/ /g;
1854                                 $line.= " $sections";
1855                                 print $fh $line."\n";
1856                         }
1857                 }
1858         }
1859         close($fh);
1861         return $result;
1864 sub create_packages_list_db {
1865     my ($ldap_handle, $sources_file) = @_ ;
1867     if (not defined $ldap_handle) { 
1868         daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1869         return;
1870     }
1871     if (not defined $sources_file) { 
1872         $sources_file = &create_sources_list($ldap_handle);
1873     }
1875     my $line;
1876     daemon_log("INFO: create_packages_list_db: start", 5); 
1878     open(CONFIG, "<$sources_file") or do {
1879         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1880         return;
1881     };
1882     
1883     # Read lines
1884     while ($line = <CONFIG>){
1885         # Unify
1886         chop($line);
1887         $line =~ s/^\s+//;
1888         $line =~ s/^\s+/ /;
1890         # Strip comments
1891         $line =~ s/#.*$//g;
1893         # Skip empty lines
1894         if ($line =~ /^\s*$/){
1895             next;
1896         }
1898         # Interpret deb line
1899         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1900             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1901             my $section;
1902             foreach $section (split(' ', $sections)){
1903                 &parse_package_info( $baseurl, $dist, $section );
1904             }
1905         }
1906     }
1908     close (CONFIG);
1910     daemon_log("INFO: create_packages_list_db: finished", 5); 
1911     return;
1914 sub run_create_packages_list_db {
1915     my ($session, $heap) = @_[SESSION, HEAP];
1916     my $task = POE::Wheel::Run->new(
1917             Program => sub {&create_packages_list_db},
1918             StdoutEvent  => "session_run_result",
1919             StderrEvent  => "session_run_debug",
1920             CloseEvent   => "session_run_done",
1921             );
1922     $heap->{task}->{ $task->ID } = $task;
1925 sub parse_package_info {
1926   my ($baseurl, $dist, $section)= @_;
1927   my ($package);
1929   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1930   $repo_dirs{ "${repo_path}/pool" } = 1;
1932   foreach $package ("Packages.gz"){
1933     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1934     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1935     parse_package( "$outdir/$dist/$section", $dist, $path );
1936   }
1937   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1940 sub get_package {
1941   my ($url, $dest)= @_;
1943   my $tpath = dirname($dest);
1944   -d "$tpath" || mkpath "$tpath";
1946   # This is ugly, but I've no time to take a look at "how it works in perl"
1947   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1948       system("gunzip -cd '$dest' > '$dest.in'");
1949       unlink($dest);
1950   } else {
1951       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1952   }
1953   return 0;
1956 sub parse_package {
1957     my ($path, $dist, $srv_path)= @_;
1958     my ($package, $version, $section, $description);
1959     my @sql_list;
1960     my $PACKAGES;
1962     if(not stat("$path.in")) {
1963         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1964         return;
1965     }
1967     open($PACKAGES, "<$path.in");
1968         if(not defined($PACKAGES)) {
1969         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
1970         return;
1971     }
1973     # Read lines
1974     while (<$PACKAGES>){
1975         my $line = $_;
1976         # Unify
1977         chop($line);
1979         # Use empty lines as a trigger
1980         if ($line =~ /^\s*$/){
1981             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1982             push(@sql_list, $sql);
1983             $package = "none";
1984             $version = "none";
1985             $section = "none";
1986             $description = "none"; 
1987             next;
1988         }
1990         # Trigger for package name
1991         if ($line =~ /^Package:\s/){
1992             ($package)= ($line =~ /^Package: (.*)$/);
1993             next;
1994         }
1996         # Trigger for version
1997         if ($line =~ /^Version:\s/){
1998             ($version)= ($line =~ /^Version: (.*)$/);
1999             next;
2000         }
2002         # Trigger for description
2003         if ($line =~ /^Description:\s/){
2004             ($description)= ($line =~ /^Description: (.*)$/);
2005             next;
2006         }
2008         # Trigger for section
2009         if ($line =~ /^Section:\s/){
2010             ($section)= ($line =~ /^Section: (.*)$/);
2011             next;
2012         }
2014         # Trigger for filename
2015         if ($line =~ /^Filename:\s/){
2016                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2017                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2018                 next;
2019         }
2020     }
2022     close( $PACKAGES );
2023     unlink( "$path.in" );
2024     
2025     $packages_list_db->exec_statementlist(\@sql_list);
2028 sub store_fileinfo {
2029   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2031   my %fileinfo = (
2032     'package' => $package,
2033     'dist' => $dist,
2034     'version' => $vers,
2035   );
2037   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2040 sub cleanup_and_extract {
2041   my $fileinfo = $repo_files{ $File::Find::name };
2043   if( defined $fileinfo ) {
2045     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2046     my $sql;
2047     my $package = $fileinfo->{ 'package' };
2048     my $newver = $fileinfo->{ 'version' };
2050     mkpath($dir);
2051     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2053     if( -f "$dir/DEBIAN/templates" ) {
2055       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2057       my $tmpl= "";
2058       {
2059           local $/=undef;
2060           open FILE, "$dir/DEBIAN/templates";
2061           $tmpl = &encode_base64(<FILE>);
2062           close FILE;
2063       }
2064       rmtree("$dir/DEBIAN/templates");
2066       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2068     } else {
2069       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2070     }
2072     my $res= $main::packages_list_db->update_dbentry($sql);
2073   }
2077 #==== MAIN = main ==============================================================
2078 #  parse commandline options
2079 Getopt::Long::Configure( "bundling" );
2080 GetOptions("h|help" => \&usage,
2081         "c|config=s" => \$cfg_file,
2082         "f|foreground" => \$foreground,
2083         "v|verbose+" => \$verbose,
2084         "no-bus+" => \$no_bus,
2085         "no-arp+" => \$no_arp,
2086            );
2088 #  read and set config parameters
2089 &check_cmdline_param ;
2090 &read_configfile;
2091 &check_pid;
2093 $SIG{CHLD} = 'IGNORE';
2095 # forward error messages to logfile
2096 if( ! $foreground ) {
2097   open( STDIN,  '+>/dev/null' );
2098   open( STDOUT, '+>&STDIN'    );
2099   open( STDERR, '+>&STDIN'    );
2102 # Just fork, if we are not in foreground mode
2103 if( ! $foreground ) { 
2104     chdir '/'                 or die "Can't chdir to /: $!";
2105     $pid = fork;
2106     setsid                    or die "Can't start a new session: $!";
2107     umask 0;
2108 } else { 
2109     $pid = $$; 
2112 # Do something useful - put our PID into the pid_file
2113 if( 0 != $pid ) {
2114     open( LOCK_FILE, ">$pid_file" );
2115     print LOCK_FILE "$pid\n";
2116     close( LOCK_FILE );
2117     if( !$foreground ) { 
2118         exit( 0 ) 
2119     };
2122 daemon_log(" ", 1);
2123 daemon_log("$0 started!", 1);
2125 if ($no_bus > 0) {
2126     $bus_activ = "false"
2131 # delete old DBsqlite lock files
2132 #unlink('/tmp/gosa_si_lock*');
2134 # connect to gosa-si job queue
2135 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2136 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2138 # connect to known_clients_db
2139 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2140 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2142 # connect to known_server_db
2143 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2144 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2146 # connect to login_usr_db
2147 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2148 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2150 # connect to fai_server_db and fai_release_db
2151 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2152 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2153 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2155 # connect to packages_list_db
2156 unlink($packages_list_file_name);
2157 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2158 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2160 # connect to messaging_db
2161 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2162 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2165 # create xml object used for en/decrypting
2166 $xml = new XML::Simple();
2168 # create socket for incoming xml messages
2170 POE::Component::Server::TCP->new(
2171         Port => $server_port,
2172         ClientInput => sub {
2173         my ($kernel, $input) = @_[KERNEL, ARG0];
2174         push(@tasks, $input);
2175         $kernel->yield("next_task");
2176         },
2177     InlineStates => {
2178         next_task => \&next_task,
2179         task_result => \&handle_task_result,
2180         task_done   => \&handle_task_done,
2181         task_debug  => \&handle_task_debug,
2182         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2183     }
2184 );
2186 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2188 # create session for repeatedly checking the job queue for jobs
2189 POE::Session->create(
2190         inline_states => {
2191                 _start => \&_start,
2192                 sig_handler => \&sig_handler,
2193                 watch_for_new_jobs => \&watch_for_new_jobs,
2194         watch_for_done_jobs => \&watch_for_done_jobs,
2195         create_packages_list_db => \&run_create_packages_list_db,
2196         create_fai_server_db => \&run_create_fai_server_db,
2197         create_fai_release_db => \&run_create_fai_release_db,
2198         session_run_result => \&session_run_result,
2199         session_run_debug => \&session_run_debug,
2200         session_run_done => \&session_run_done,
2201         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2202         }
2203 );
2206 # import all modules
2207 &import_modules;
2209 # check wether all modules are gosa-si valid passwd check
2211 POE::Kernel->run();
2212 exit;