Code

closes #405
[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         $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");
973 sub sig_handler {
974         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
975         daemon_log("0 INFO got signal '$signal'", 1); 
976         $kernel->sig_handled();
977         return;
980 sub next_task {
981     my ($session, $heap) = @_[SESSION, HEAP];
983     while ( keys( %{ $heap->{task} } ) < $max_children ) {
984         my $next_task = shift @tasks;
985         last unless defined $next_task;
987         my $task = POE::Wheel::Run->new(
988                 Program => sub { process_task($session, $heap, $next_task) },
989                 StdioFilter => POE::Filter::Reference->new(),
990                 StdoutEvent  => "task_result",
991                 StderrEvent  => "task_debug",
992                 CloseEvent   => "task_done",
993                );
995         $heap->{task}->{ $task->ID } = $task;
996     }
999 sub handle_task_result {
1000     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1001     my $client_answer = $result->{'answer'};
1002     if( $client_answer =~ s/session_id=(\d+)$// ) {
1003         my $session_id = $1;
1004         if( defined $session_id ) {
1005             my $session_reference = $kernel->ID_id_to_session($session_id);
1006             if( defined $session_reference ) {
1007                 $heap = $session_reference->get_heap();
1008             }
1009         }
1011         if(exists $heap->{'client'}) {
1012             $heap->{'client'}->put($client_answer);
1013         }
1014     }
1015     $kernel->sig(CHLD => "child_reap");
1018 sub handle_task_debug {
1019     my $result = $_[ARG0];
1020     print STDERR "$result\n";
1023 sub handle_task_done {
1024     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1025     delete $heap->{task}->{$task_id};
1026     $kernel->yield("next_task");
1029 sub process_task {
1030     no strict "refs";
1031     my ($session, $heap, $input) = @_;
1032     my $session_id = $session->ID;
1033     my ($msg, $msg_hash, $module);
1034     my $error = 0;
1035     my $answer_l;
1036     my ($answer_header, @answer_target_l, $answer_source);
1037     my $client_answer = "";
1039     daemon_log("", 5); 
1040     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1041     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1043     ####################
1044     # check incoming msg
1045     # msg is from a new client or gosa
1046     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1047     # msg is from a gosa-si-server or gosa-si-bus
1048     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1050     }
1051     # msg is from a gosa-si-client
1052     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1054     }
1055     # an error occurred
1056     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1058         # could not understand a msg from its server the client cause a re-registering process
1059         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);
1060         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1061         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1062         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1063             my $host_name = $hit->{'hostname'};
1064             my $host_key = $hit->{'hostkey'};
1065             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1066             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1067             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1068         }
1069         $error++;
1070     }
1072     ######################
1073     # process incoming msg
1074     if( $error == 0) {
1075         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1076                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1077         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1078         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1080         if ( 0 < @{$answer_l} ) {
1081             my $answer_str = join("\n", @{$answer_l});
1082             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1083         }
1084     }
1085     if( !$answer_l ) { $error++ };
1087     ########
1088     # answer
1089     if( $error == 0 ) {
1091         foreach my $answer ( @{$answer_l} ) {
1092             # for each answer in answer list
1093             
1094             # check outgoing msg to xml validity
1095             my $answer_hash = &check_outgoing_xml_validity($answer);
1096             if( not defined $answer_hash ) {
1097                 next;
1098             }
1099             
1100             $answer_header = @{$answer_hash->{'header'}}[0];
1101             @answer_target_l = @{$answer_hash->{'target'}};
1102             $answer_source = @{$answer_hash->{'source'}}[0];
1104             # deliver msg to all targets 
1105             foreach my $answer_target ( @answer_target_l ) {
1107                 # targets of msg are all gosa-si-clients in known_clients_db
1108                 if( $answer_target eq "*" ) {
1109                     # answer is for all clients
1110                     my $sql_statement= "SELECT * FROM known_clients";
1111                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1112                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1113                         my $host_name = $hit->{hostname};
1114                         my $host_key = $hit->{hostkey};
1115                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1116                         &update_jobdb_status_for_send_msgs($answer, $error);
1117                     }
1118                 }
1120                 # targets of msg are all gosa-si-server in known_server_db
1121                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1122                     # answer is for all server in known_server
1123                     my $sql_statement= "SELECT * FROM known_server";
1124                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1125                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1126                         my $host_name = $hit->{hostname};
1127                         my $host_key = $hit->{hostkey};
1128                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1129                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1130                         &update_jobdb_status_for_send_msgs($answer, $error);
1131                     }
1132                 }
1134                 # target of msg is GOsa
1135                                 elsif( $answer_target eq "GOSA" ) {
1136                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1137                                         my $add_on = "";
1138                     if( defined $session_id ) {
1139                         $add_on = ".session_id=$session_id";
1140                     }
1141                     # answer is for GOSA and has to returned to connected client
1142                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1143                     $client_answer = $gosa_answer.$add_on;
1144                 }
1146                 # target of msg is job queue at this host
1147                 elsif( $answer_target eq "JOBDB") {
1148                     $answer =~ /<header>(\S+)<\/header>/;   
1149                     my $header;
1150                     if( defined $1 ) { $header = $1; }
1151                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1152                     &update_jobdb_status_for_send_msgs($answer, $error);
1153                 }
1155                 # target of msg is a mac address
1156                 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 ) {
1157                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1158                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1159                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1160                     my $found_ip_flag = 0;
1161                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1162                         my $host_name = $hit->{hostname};
1163                         my $host_key = $hit->{hostkey};
1164                         $answer =~ s/$answer_target/$host_name/g;
1165                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1166                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1167                         &update_jobdb_status_for_send_msgs($answer, $error);
1168                         $found_ip_flag++ ;
1169                     }   
1170                     if( $found_ip_flag == 0) {
1171                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1172                         if( $bus_activ eq "true" ) { 
1173                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1174                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1175                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1176                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1177                                 my $bus_address = $hit->{hostname};
1178                                 my $bus_key = $hit->{hostkey};
1179                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1180                                 &update_jobdb_status_for_send_msgs($answer, $error);
1181                                 last;
1182                             }
1183                         }
1185                     }
1187                 #  answer is for one specific host   
1188                 } else {
1189                     # get encrypt_key
1190                     my $encrypt_key = &get_encrypt_key($answer_target);
1191                     if( not defined $encrypt_key ) {
1192                         # unknown target, forward msg to bus
1193                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1194                         if( $bus_activ eq "true" ) { 
1195                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1196                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1197                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1198                             my $res_length = keys( %{$query_res} );
1199                             if( $res_length == 0 ){
1200                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1201                                         "no bus found in known_server", 3);
1202                             }
1203                             else {
1204                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1205                                     my $bus_key = $hit->{hostkey};
1206                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1207                                     &update_jobdb_status_for_send_msgs($answer, $error);
1208                                 }
1209                             }
1210                         }
1211                         next;
1212                     }
1213                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1214                     &update_jobdb_status_for_send_msgs($answer, $error);
1215                 }
1216             }
1217         }
1218     }
1220     my $filter = POE::Filter::Reference->new();
1221     my %result = ( 
1222             status => "seems ok to me",
1223             answer => $client_answer,
1224             );
1226     my $output = $filter->put( [ \%result ] );
1227     print @$output;
1233 sub trigger_db_loop {
1234         my ($kernel) = @_ ;
1235         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1236     $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1239 sub watch_for_done_jobs {
1240     my ($kernel,$heap) = @_[KERNEL, HEAP];
1242     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1243         " WHERE status='done'";
1244         my $res = $job_db->select_dbentry( $sql_statement );
1246     while( my ($id, $hit) = each %{$res} ) {
1247         my $jobdb_id = $hit->{id};
1248         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1249         my $res = $job_db->del_dbentry($sql_statement);
1250     }
1252     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1255 sub watch_for_new_jobs {
1256         my ($kernel,$heap) = @_[KERNEL, HEAP];
1258         # check gosa job queue for jobs with executable timestamp
1259     my $timestamp = &get_time();
1260     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1261         " WHERE status='waiting' AND timestamp<'$timestamp'";
1262         my $res = $job_db->select_dbentry( $sql_statement );
1264         while( my ($id, $hit) = each %{$res} ) {         
1265                 my $jobdb_id = $hit->{id};
1266                 my $macaddress = $hit->{'macaddress'};
1267         my $job_msg = $hit->{'xmlmessage'};
1268         daemon_log("J DEBUG: its time to execute $job_msg", 7); 
1269         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1270                 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1271                 # expect macaddress is unique!!!!!!
1272                 my $target = $res_hash->{1}->{hostname};
1274                 # change header
1275         $job_msg =~ s/<header>job_/<header>gosa_/;
1277                 # add sqlite_id 
1278         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1280         $job_msg =~ /<header>(\S+)<\/header>/;
1281         my $header = $1 ;
1282                 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1284         # update status in job queue to 'processing'
1285         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1286         my $res = $job_db->update_dbentry($sql_statement);
1287     }
1289         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1293 sub get_ldap_handle {
1294         my ($session_id) = @_;
1295         my $heap;
1296         my $ldap_handle;
1298         if (not defined $session_id ) { $session_id = 0 };
1300         if ($session_id == 0) {
1301                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1302                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1303                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1305         } else {
1306                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1307                 if( defined $session_reference ) {
1308                         $heap = $session_reference->get_heap();
1309                 }
1311                 if (not defined $heap) {
1312                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1313                         return;
1314                 }
1316                 # TODO: This "if" is nonsense, because it doesn't prove that the
1317                 #       used handle is still valid - or if we've to reconnect...
1318                 #if (not exists $heap->{ldap_handle}) {
1319                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1320                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1321                         $heap->{ldap_handle} = $ldap_handle;
1322                 #}
1323         }
1324         return $ldap_handle;
1328 sub change_fai_state {
1329     my ($st, $targets, $session_id) = @_;
1330     $session_id = 0 if not defined $session_id;
1331     # Set FAI state to localboot
1332     my %mapActions= (
1333         reboot    => '',
1334         update    => 'softupdate',
1335         localboot => 'localboot',
1336         reinstall => 'install',
1337         rescan    => '',
1338         wake      => '',
1339         memcheck  => 'memcheck',
1340         sysinfo   => 'sysinfo',
1341         install   => 'install',
1342     );
1344     # Return if this is unknown
1345     if (!exists $mapActions{ $st }){
1346         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1347       return;
1348     }
1350     my $state= $mapActions{ $st };
1352     my $ldap_handle = &get_ldap_handle($session_id);
1353     if( defined($ldap_handle) ) {
1355       # Build search filter for hosts
1356         my $search= "(&(objectClass=GOhard)";
1357         foreach (@{$targets}){
1358             $search.= "(macAddress=$_)";
1359         }
1360         $search.= ")";
1362       # If there's any host inside of the search string, procress them
1363         if (!($search =~ /macAddress/)){
1364             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1365             return;
1366         }
1368       # Perform search for Unit Tag
1369       my $mesg = $ldap_handle->search(
1370           base   => $ldap_base,
1371           scope  => 'sub',
1372           attrs  => ['dn', 'FAIstate', 'objectClass'],
1373           filter => "$search"
1374           );
1376       if ($mesg->count) {
1377         my @entries = $mesg->entries;
1378         foreach my $entry (@entries) {
1379           # Only modify entry if it is not set to '$state'
1380           if ($entry->get_value("FAIstate") ne "$state"){
1381             daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1382             my $result;
1383             my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1384             if (exists $tmp{'FAIobject'}){
1385               if ($state eq ''){
1386                 $result= $ldap_handle->modify($entry->dn, changes => [
1387                             delete => [ FAIstate => [] ] ]);
1388               } else {
1389                 $result= $ldap_handle->modify($entry->dn, changes => [
1390                             replace => [ FAIstate => $state ] ]);
1391               }
1392             } elsif ($state ne ''){
1393               $result= $ldap_handle->modify($entry->dn, changes => [
1394                           add     => [ objectClass => 'FAIobject' ],
1395                           add     => [ FAIstate => $state ] ]);
1396             }
1398             # Errors?
1399             if ($result->code){
1400               daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1401             }
1403           } else {
1404             daemon_log("$session_id DEBUG FAIstate at host '$_' already at state '$st'", 7); 
1405           }  
1406         }
1407       }
1408     # if no ldap handle defined
1409     } else {
1410         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1411     }
1416 sub change_goto_state {
1417     my ($st, $targets, $session_id) = @_;
1418     $session_id = 0  if not defined $session_id;
1420     # Switch on or off?
1421     my $state= $st eq 'active' ? 'active': 'locked';
1423     my $ldap_handle = &get_ldap_handle($session_id);
1424     if( defined($ldap_handle) ) {
1426       # Build search filter for hosts
1427       my $search= "(&(objectClass=GOhard)";
1428       foreach (@{$targets}){
1429         $search.= "(macAddress=$_)";
1430       }
1431       $search.= ")";
1433       # If there's any host inside of the search string, procress them
1434       if (!($search =~ /macAddress/)){
1435         return;
1436       }
1438       # Perform search for Unit Tag
1439       my $mesg = $ldap_handle->search(
1440           base   => $ldap_base,
1441           scope  => 'sub',
1442           attrs  => ['dn', 'gotoMode'],
1443           filter => "$search"
1444           );
1446       if ($mesg->count) {
1447         my @entries = $mesg->entries;
1448         foreach my $entry (@entries) {
1450           # Only modify entry if it is not set to '$state'
1451           if ($entry->get_value("gotoMode") ne $state){
1453             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1454             my $result;
1455             $result= $ldap_handle->modify($entry->dn, changes => [
1456                                                 replace => [ gotoMode => $state ] ]);
1458             # Errors?
1459             if ($result->code){
1460               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1461             }
1463           }
1464         }
1465       }
1467     }
1471 sub create_fai_server_db {
1472     my ($table_name, $kernel) = @_;
1473         my $result;
1474     my $ldap_handle = &get_ldap_handle();
1475         if(defined($ldap_handle)) {
1476                 daemon_log("INFO: create_fai_server_db: start", 5);
1477                 my $mesg= $ldap_handle->search(
1478                         base   => $ldap_base,
1479                         scope  => 'sub',
1480                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1481                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1482                 );
1483                 if($mesg->{'resultCode'} == 0 &&
1484                    $mesg->count != 0) {
1485                    foreach my $entry (@{$mesg->{entries}}) {
1486                            if($entry->exists('FAIrepository')) {
1487                                    # Add an entry for each Repository configured for server
1488                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1489                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1490                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1491                                                    $result= $fai_server_db->add_dbentry( { 
1492                                                                    table => $table_name,
1493                                                                    primkey => ['server', 'release', 'tag'],
1494                                                                    server => $tmp_url,
1495                                                                    release => $tmp_release,
1496                                                                    sections => $tmp_sections,
1497                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1498                                                            } );
1499                                            }
1500                                    }
1501                            }
1502                    }
1503                 daemon_log("INFO: create_fai_server_db: finished", 5);
1505                 # TODO: Find a way to post the 'create_packages_list_db' event
1506                 &create_packages_list_db($ldap_handle);
1507         }       
1508     
1509     $ldap_handle->disconnect;
1510         return $result;
1513 sub run_create_fai_server_db {
1514     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1515     my $task = POE::Wheel::Run->new(
1516             Program => sub { &create_fai_server_db($table_name,$kernel) },
1517             StdoutEvent  => "session_run_result",
1518             StderrEvent  => "session_run_debug",
1519             CloseEvent   => "session_run_done",
1520             );
1522     $heap->{task}->{ $task->ID } = $task;
1523     return;
1527 sub create_fai_release_db {
1528         my ($table_name) = @_;
1529         my $result;
1531     my $ldap_handle = &get_ldap_handle();
1532         if(defined($ldap_handle)) {
1533                 daemon_log("INFO: create_fai_release_db: start",5);
1534                 my $mesg= $ldap_handle->search(
1535                         base   => $ldap_base,
1536                         scope  => 'sub',
1537                         attrs  => [],
1538                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1539                 );
1540                 if($mesg->{'resultCode'} == 0 &&
1541                         $mesg->count != 0) {
1542                         # Walk through all possible FAI container ou's
1543                         my @sql_list;
1544                         my $timestamp= &get_time();
1545                         foreach my $ou (@{$mesg->{entries}}) {
1546                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1547                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1548                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1549                                         if(@tmp_array) {
1550                                                 foreach my $entry (@tmp_array) {
1551                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1552                                                                 my $sql= 
1553                                                                 "INSERT INTO $table_name "
1554                                                                 ."(timestamp, release, class, type, state) VALUES ("
1555                                                                 .$timestamp.","
1556                                                                 ."'".$entry->{'release'}."',"
1557                                                                 ."'".$entry->{'class'}."',"
1558                                                                 ."'".$entry->{'type'}."',"
1559                                                                 ."'".$entry->{'state'}."')";
1560                                                                 push @sql_list, $sql;
1561                                                         }
1562                                                 }
1563                                         }
1564                                 }
1565                         }
1566                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1567                         if(@sql_list) {
1568                                 unshift @sql_list, "DELETE FROM $table_name";
1569                                 $fai_server_db->exec_statementlist(\@sql_list);
1570                         }
1571                         daemon_log("DEBUG: Done with inserting",6);
1572                 }
1573                 daemon_log("INFO: create_fai_release_db: finished",5);
1574         }
1575     $ldap_handle->disconnect;
1576         return $result;
1578 sub run_create_fai_release_db {
1579     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1580     my $task = POE::Wheel::Run->new(
1581             Program => sub { &create_fai_release_db($table_name) },
1582             StdoutEvent  => "session_run_result",
1583             StderrEvent  => "session_run_debug",
1584             CloseEvent   => "session_run_done",
1585             );
1587     $heap->{task}->{ $task->ID } = $task;
1588     return;
1591 sub get_fai_types {
1592         my $tmp_classes = shift || return undef;
1593         my @result;
1595         foreach my $type(keys %{$tmp_classes}) {
1596                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1597                         my $entry = {
1598                                 type => $type,
1599                                 state => $tmp_classes->{$type}[0],
1600                         };
1601                         push @result, $entry;
1602                 }
1603         }
1605         return @result;
1608 sub get_fai_state {
1609         my $result = "";
1610         my $tmp_classes = shift || return $result;
1612         foreach my $type(keys %{$tmp_classes}) {
1613                 if(defined($tmp_classes->{$type}[0])) {
1614                         $result = $tmp_classes->{$type}[0];
1615                         
1616                 # State is equal for all types in class
1617                         last;
1618                 }
1619         }
1621         return $result;
1624 sub resolve_fai_classes {
1625         my ($fai_base, $ldap_handle) = @_;
1626         my $result;
1627         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1628         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1629         my $fai_classes;
1631         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1632         my $mesg= $ldap_handle->search(
1633                 base   => $fai_base,
1634                 scope  => 'sub',
1635                 attrs  => ['cn','objectClass','FAIstate'],
1636                 filter => $fai_filter,
1637         );
1638         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1640         if($mesg->{'resultCode'} == 0 &&
1641                 $mesg->count != 0) {
1642                 foreach my $entry (@{$mesg->{entries}}) {
1643                         if($entry->exists('cn')) {
1644                                 my $tmp_dn= $entry->dn();
1646                                 # Skip classname and ou dn parts for class
1647                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1649                                 # Skip classes without releases
1650                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1651                                         next;
1652                                 }
1654                                 my $tmp_cn= $entry->get_value('cn');
1655                                 my $tmp_state= $entry->get_value('FAIstate');
1657                                 my $tmp_type;
1658                                 # Get FAI type
1659                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1660                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1661                                                 $tmp_type= $oclass;
1662                                                 last;
1663                                         }
1664                                 }
1666                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1667                                         # A Subrelease
1668                                         my @sub_releases = split(/,/, $tmp_release);
1670                                         # Walk through subreleases and build hash tree
1671                                         my $hash;
1672                                         while(my $tmp_sub_release = pop @sub_releases) {
1673                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1674                                         }
1675                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1676                                 } else {
1677                                         # A branch, no subrelease
1678                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1679                                 }
1680                         } elsif (!$entry->exists('cn')) {
1681                                 my $tmp_dn= $entry->dn();
1682                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1684                                 # Skip classes without releases
1685                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1686                                         next;
1687                                 }
1689                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1690                                         # A Subrelease
1691                                         my @sub_releases= split(/,/, $tmp_release);
1693                                         # Walk through subreleases and build hash tree
1694                                         my $hash;
1695                                         while(my $tmp_sub_release = pop @sub_releases) {
1696                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1697                                         }
1698                                         # Remove the last two characters
1699                                         chop($hash);
1700                                         chop($hash);
1702                                         eval('$fai_classes->'.$hash.'= {}');
1703                                 } else {
1704                                         # A branch, no subrelease
1705                                         if(!exists($fai_classes->{$tmp_release})) {
1706                                                 $fai_classes->{$tmp_release} = {};
1707                                         }
1708                                 }
1709                         }
1710                 }
1712                 # The hash is complete, now we can honor the copy-on-write based missing entries
1713                 foreach my $release (keys %$fai_classes) {
1714                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1715                 }
1716         }
1717         return $result;
1720 sub apply_fai_inheritance {
1721        my $fai_classes = shift || return {};
1722        my $tmp_classes;
1724        # Get the classes from the branch
1725        foreach my $class (keys %{$fai_classes}) {
1726                # Skip subreleases
1727                if($class =~ /^ou=.*$/) {
1728                        next;
1729                } else {
1730                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1731                }
1732        }
1734        # Apply to each subrelease
1735        foreach my $subrelease (keys %{$fai_classes}) {
1736                if($subrelease =~ /ou=/) {
1737                        foreach my $tmp_class (keys %{$tmp_classes}) {
1738                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1739                                        $fai_classes->{$subrelease}->{$tmp_class} =
1740                                        deep_copy($tmp_classes->{$tmp_class});
1741                                } else {
1742                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1743                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1744                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1745                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1746                                                }
1747                                        }
1748                                }
1749                        }
1750                }
1751        }
1753        # Find subreleases in deeper levels
1754        foreach my $subrelease (keys %{$fai_classes}) {
1755                if($subrelease =~ /ou=/) {
1756                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1757                                if($subsubrelease =~ /ou=/) {
1758                                        apply_fai_inheritance($fai_classes->{$subrelease});
1759                                }
1760                        }
1761                }
1762        }
1764        return $fai_classes;
1767 sub get_fai_release_entries {
1768         my $tmp_classes = shift || return;
1769         my $parent = shift || "";
1770         my @result = shift || ();
1772         foreach my $entry (keys %{$tmp_classes}) {
1773                 if(defined($entry)) {
1774                         if($entry =~ /^ou=.*$/) {
1775                                 my $release_name = $entry;
1776                                 $release_name =~ s/ou=//g;
1777                                 if(length($parent)>0) {
1778                                         $release_name = $parent."/".$release_name;
1779                                 }
1780                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1781                                 foreach my $bufentry(@bufentries) {
1782                                         push @result, $bufentry;
1783                                 }
1784                         } else {
1785                                 my @types = get_fai_types($tmp_classes->{$entry});
1786                                 foreach my $type (@types) {
1787                                         push @result, 
1788                                         {
1789                                                 'class' => $entry,
1790                                                 'type' => $type->{'type'},
1791                                                 'release' => $parent,
1792                                                 'state' => $type->{'state'},
1793                                         };
1794                                 }
1795                         }
1796                 }
1797         }
1799         return @result;
1802 sub deep_copy {
1803         my $this = shift;
1804         if (not ref $this) {
1805                 $this;
1806         } elsif (ref $this eq "ARRAY") {
1807                 [map deep_copy($_), @$this];
1808         } elsif (ref $this eq "HASH") {
1809                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1810         } else { die "what type is $_?" }
1814 sub session_run_result {
1815     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1816     $kernel->sig(CHLD => "child_reap");
1819 sub session_run_debug {
1820     my $result = $_[ARG0];
1821     print STDERR "$result\n";
1824 sub session_run_done {
1825     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1826     delete $heap->{task}->{$task_id};
1829 sub create_sources_list {
1830     my ($ldap_handle) = @_;
1831         my $result="/tmp/gosa_si_tmp_sources_list";
1833         # Remove old file
1834         if(stat($result)) {
1835                 unlink($result);
1836         }
1838         my $fh;
1839         open($fh, ">$result") or return undef;
1840         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1841                 my $mesg=$ldap_handle->search(
1842                                 base    => $ldap_server_dn,
1843                                 scope   => 'base',
1844                                 attrs   => 'FAIrepository',
1845                                 filter  => 'objectClass=FAIrepositoryServer'
1846                                 );
1847                 if($mesg->count) {
1848                         foreach my $entry(@{$mesg->{'entries'}}) {
1849                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1850                                 my $line = "deb $server $release";
1851                                 $sections =~ s/,/ /g;
1852                                 $line.= " $sections";
1853                                 print $fh $line."\n";
1854                         }
1855                 }
1856         }
1857         close($fh);
1859         return $result;
1862 sub create_packages_list_db {
1863     my ($ldap_handle, $sources_file) = @_ ;
1865     if (not defined $ldap_handle) { 
1866         daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1867         return;
1868     }
1869     if (not defined $sources_file) { 
1870         $sources_file = &create_sources_list($ldap_handle);
1871     }
1873     my $line;
1874     daemon_log("INFO: create_packages_list_db: start", 5); 
1876     open(CONFIG, "<$sources_file") or do {
1877         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1878         return;
1879     };
1880     
1881     # Read lines
1882     while ($line = <CONFIG>){
1883         # Unify
1884         chop($line);
1885         $line =~ s/^\s+//;
1886         $line =~ s/^\s+/ /;
1888         # Strip comments
1889         $line =~ s/#.*$//g;
1891         # Skip empty lines
1892         if ($line =~ /^\s*$/){
1893             next;
1894         }
1896         # Interpret deb line
1897         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1898             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1899             my $section;
1900             foreach $section (split(' ', $sections)){
1901                 &parse_package_info( $baseurl, $dist, $section );
1902             }
1903         }
1904     }
1906     close (CONFIG);
1908     daemon_log("INFO: create_packages_list_db: finished", 5); 
1909     return;
1912 sub run_create_packages_list_db {
1913     my ($session, $heap) = @_[SESSION, HEAP];
1914     my $task = POE::Wheel::Run->new(
1915             Program => sub {&create_packages_list_db},
1916             StdoutEvent  => "session_run_result",
1917             StderrEvent  => "session_run_debug",
1918             CloseEvent   => "session_run_done",
1919             );
1920     $heap->{task}->{ $task->ID } = $task;
1923 sub parse_package_info {
1924   my ($baseurl, $dist, $section)= @_;
1925   my ($package);
1927   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1928   $repo_dirs{ "${repo_path}/pool" } = 1;
1930   foreach $package ("Packages.gz"){
1931     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1932     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1933     parse_package( "$outdir/$dist/$section", $dist, $path );
1934   }
1935   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1938 sub get_package {
1939   my ($url, $dest)= @_;
1941   my $tpath = dirname($dest);
1942   -d "$tpath" || mkpath "$tpath";
1944   # This is ugly, but I've no time to take a look at "how it works in perl"
1945   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1946       system("gunzip -cd '$dest' > '$dest.in'");
1947       unlink($dest);
1948   } else {
1949       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1950   }
1951   return 0;
1954 sub parse_package {
1955     my ($path, $dist, $srv_path)= @_;
1956     my ($package, $version, $section, $description);
1957     my @sql_list;
1958     my $PACKAGES;
1960     if(not stat("$path.in")) {
1961         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1962         return;
1963     }
1965     open($PACKAGES, "<$path.in");
1966         if(not defined($PACKAGES)) {
1967         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
1968         return;
1969     }
1971     # Read lines
1972     while (<$PACKAGES>){
1973         my $line = $_;
1974         # Unify
1975         chop($line);
1977         # Use empty lines as a trigger
1978         if ($line =~ /^\s*$/){
1979             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1980             push(@sql_list, $sql);
1981             $package = "none";
1982             $version = "none";
1983             $section = "none";
1984             $description = "none"; 
1985             next;
1986         }
1988         # Trigger for package name
1989         if ($line =~ /^Package:\s/){
1990             ($package)= ($line =~ /^Package: (.*)$/);
1991             next;
1992         }
1994         # Trigger for version
1995         if ($line =~ /^Version:\s/){
1996             ($version)= ($line =~ /^Version: (.*)$/);
1997             next;
1998         }
2000         # Trigger for description
2001         if ($line =~ /^Description:\s/){
2002             ($description)= ($line =~ /^Description: (.*)$/);
2003             next;
2004         }
2006         # Trigger for section
2007         if ($line =~ /^Section:\s/){
2008             ($section)= ($line =~ /^Section: (.*)$/);
2009             next;
2010         }
2012         # Trigger for filename
2013         if ($line =~ /^Filename:\s/){
2014                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2015                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2016                 next;
2017         }
2018     }
2020     close( $PACKAGES );
2021     unlink( "$path.in" );
2022     
2023     $packages_list_db->exec_statementlist(\@sql_list);
2026 sub store_fileinfo {
2027   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2029   my %fileinfo = (
2030     'package' => $package,
2031     'dist' => $dist,
2032     'version' => $vers,
2033   );
2035   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2038 sub cleanup_and_extract {
2039   my $fileinfo = $repo_files{ $File::Find::name };
2041   if( defined $fileinfo ) {
2043     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2044     my $sql;
2045     my $package = $fileinfo->{ 'package' };
2046     my $newver = $fileinfo->{ 'version' };
2048     mkpath($dir);
2049     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2051     if( -f "$dir/DEBIAN/templates" ) {
2053       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2055       my $tmpl= "";
2056       {
2057           local $/=undef;
2058           open FILE, "$dir/DEBIAN/templates";
2059           $tmpl = &encode_base64(<FILE>);
2060           close FILE;
2061       }
2062       rmtree("$dir/DEBIAN/templates");
2064       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2066     } else {
2067       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2068     }
2070     my $res= $main::packages_list_db->update_dbentry($sql);
2071   }
2075 #==== MAIN = main ==============================================================
2076 #  parse commandline options
2077 Getopt::Long::Configure( "bundling" );
2078 GetOptions("h|help" => \&usage,
2079         "c|config=s" => \$cfg_file,
2080         "f|foreground" => \$foreground,
2081         "v|verbose+" => \$verbose,
2082         "no-bus+" => \$no_bus,
2083         "no-arp+" => \$no_arp,
2084            );
2086 #  read and set config parameters
2087 &check_cmdline_param ;
2088 &read_configfile;
2089 &check_pid;
2091 $SIG{CHLD} = 'IGNORE';
2093 # forward error messages to logfile
2094 if( ! $foreground ) {
2095   open( STDIN,  '+>/dev/null' );
2096   open( STDOUT, '+>&STDIN'    );
2097   open( STDERR, '+>&STDIN'    );
2100 # Just fork, if we are not in foreground mode
2101 if( ! $foreground ) { 
2102     chdir '/'                 or die "Can't chdir to /: $!";
2103     $pid = fork;
2104     setsid                    or die "Can't start a new session: $!";
2105     umask 0;
2106 } else { 
2107     $pid = $$; 
2110 # Do something useful - put our PID into the pid_file
2111 if( 0 != $pid ) {
2112     open( LOCK_FILE, ">$pid_file" );
2113     print LOCK_FILE "$pid\n";
2114     close( LOCK_FILE );
2115     if( !$foreground ) { 
2116         exit( 0 ) 
2117     };
2120 daemon_log(" ", 1);
2121 daemon_log("$0 started!", 1);
2123 if ($no_bus > 0) {
2124     $bus_activ = "false"
2127 # connect to gosa-si job queue
2128 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2129 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2131 # connect to known_clients_db
2132 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2133 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2135 # connect to known_server_db
2136 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2137 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2139 # connect to login_usr_db
2140 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2141 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2143 # connect to fai_server_db and fai_release_db
2144 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2145 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2146 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2148 # connect to packages_list_db
2149 unlink($packages_list_file_name);
2150 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2151 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2153 # connect to messaging_db
2154 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2155 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2158 # create xml object used for en/decrypting
2159 $xml = new XML::Simple();
2161 # create socket for incoming xml messages
2163 POE::Component::Server::TCP->new(
2164         Port => $server_port,
2165         ClientInput => sub {
2166         my ($kernel, $input) = @_[KERNEL, ARG0];
2167         push(@tasks, $input);
2168         $kernel->yield("next_task");
2169         },
2170     InlineStates => {
2171         next_task => \&next_task,
2172         task_result => \&handle_task_result,
2173         task_done   => \&handle_task_done,
2174         task_debug  => \&handle_task_debug,
2175         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2176     }
2177 );
2179 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2181 # create session for repeatedly checking the job queue for jobs
2182 POE::Session->create(
2183         inline_states => {
2184                 _start => \&_start,
2185                 sig_handler => \&sig_handler,
2186                 watch_for_new_jobs => \&watch_for_new_jobs,
2187         watch_for_done_jobs => \&watch_for_done_jobs,
2188         create_packages_list_db => \&run_create_packages_list_db,
2189         create_fai_server_db => \&run_create_fai_server_db,
2190         create_fai_release_db => \&run_create_fai_release_db,
2191         session_run_result => \&session_run_result,
2192         session_run_debug => \&session_run_debug,
2193         session_run_done => \&session_run_done,
2194         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2195         }
2196 );
2199 # import all modules
2200 &import_modules;
2202 # check wether all modules are gosa-si valid passwd check
2204 POE::Kernel->run();
2205 exit;