Code

4330970853c444832319b31ba1135a9251bfb4f8
[gosa.git] / gosa-si / gosa-si-server-nobus
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 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
57 my $modules_path = "/usr/lib/gosa-si/modules";
58 use lib "/usr/lib/gosa-si/modules";
60 # revision number of server and program name
61 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
62 my $server_headURL;
63 my $server_revision;
64 my $server_status;
65 our $prg= basename($0);
67 our $global_kernel;
68 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
69 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($known_modules);
74 my ($pid_file, $procid, $pid, $log_file);
75 my ($arp_activ, $arp_fifo);
76 my ($xml);
77 my $sources_list;
78 my $max_clients;
79 my %repo_files=();
80 my $repo_path;
81 my %repo_dirs=();
82 # variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $SIPackages_key, 
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
87 );
89 # additional variable which should be globaly accessable
90 our $server_address;
91 our $server_mac_address;
92 our $bus_address;
93 our $gosa_address;
94 our $no_bus;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # specifies the verbosity of the daemon_log
103 $verbose = 0 ;
105 # if foreground is not null, script will be not forked to background
106 $foreground = 0 ;
108 # specifies the timeout seconds while checking the online status of a registrating client
109 $ping_timeout = 5;
111 $no_bus = 0;
112 $bus_activ = "true";
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
123         "timestamp DEFAULT 'none'", 
124         "headertag DEFAULT 'none'",
125                 "targettag DEFAULT 'none'",
126         "xmlmessage DEFAULT 'none'",
127         "module DEFAULT 'none'",
128         );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
135                 "timestamp DEFAULT 'none'", 
136                 "status DEFAULT 'none'", 
137                 "result DEFAULT 'none'", 
138                 "progress DEFAULT 'none'", 
139         "headertag DEFAULT 'none'", 
140                 "targettag DEFAULT 'none'", 
141                 "xmlmessage DEFAULT 'none'", 
142                 "macaddress DEFAULT 'none'",
143                 "plainname DEFAULT 'none'",
144                 );
146 # holds all other gosa-sd as well as the gosa-sd-bus
147 our $known_server_db;
148 our $known_server_tn = "known_server";
149 my $known_server_file_name;
150 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
152 # holds all registrated clients
153 our $known_clients_db;
154 our $known_clients_tn = "known_clients";
155 my $known_clients_file_name;
156 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
158 # holds all logged in user at each client 
159 our $login_users_db;
160 our $login_users_tn = "login_users";
161 my $login_users_file_name;
162 my @login_users_col_names = ("client", "user", "timestamp");
164 # holds all fai server, the debian release and tag
165 our $fai_server_db;
166 our $fai_server_tn = "fai_server"; 
167 my $fai_server_file_name;
168 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
170 our $fai_release_db;
171 our $fai_release_tn = "fai_release"; 
172 my $fai_release_file_name;
173 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
175 # holds all packages available from different repositories
176 our $packages_list_db;
177 our $packages_list_tn = "packages_list";
178 my $packages_list_file_name;
179 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
180 my $outdir = "/tmp/packages_list_db";
181 my $arch = "i386"; 
183 # holds all messages which should be delivered to a user
184 our $messaging_db;
185 our $messaging_tn = "messaging"; 
186 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
187         "flag", "direction", "delivery_time", "message", "timestamp" );
188 my $messaging_file_name;
190 # path to directory to store client install log files
191 our $client_fai_log_dir = "/var/log/fai"; 
193 # queue which stores taskes until one of the $max_children children are ready to process the task
194 my @tasks = qw();
195 my @msgs_to_decrypt = qw();
196 my $max_children = 2;
199 %cfg_defaults = (
200 "general" => {
201     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
202     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
203     },
204 "bus" => {
205     "activ" => [\$bus_activ, "true"],
206     },
207 "server" => {
208     "port" => [\$server_port, "20081"],
209     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
210     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
211     "incoming"      => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
212     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
213     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
214     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
215     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
216     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
217     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
218     "repo-path" => [\$repo_path, '/srv/www/repository'],
219     "ldap-uri" => [\$ldap_uri, ""],
220     "ldap-base" => [\$ldap_base, ""],
221     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
222     "ldap-admin-password" => [\$ldap_admin_password, ""],
223     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
224     "max-clients" => [\$max_clients, 10],
225     },
226 "GOsaPackages" => {
227     "ip" => [\$gosa_ip, "0.0.0.0"],
228     "port" => [\$gosa_port, "20082"],
229     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
230     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
231     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
232     "key" => [\$GosaPackages_key, "none"],
233     },
234 "SIPackages" => {
235     "key" => [\$SIPackages_key, "none"],
236     },
237 );
240 #===  FUNCTION  ================================================================
241 #         NAME:  usage
242 #   PARAMETERS:  nothing
243 #      RETURNS:  nothing
244 #  DESCRIPTION:  print out usage text to STDERR
245 #===============================================================================
246 sub usage {
247     print STDERR << "EOF" ;
248 usage: $prg [-hvf] [-c config]
250            -h        : this (help) message
251            -c <file> : config file
252            -f        : foreground, process will not be forked to background
253            -v        : be verbose (multiple to increase verbosity)
254            -no-bus   : starts $prg without connection to bus
255            -no-arp   : starts $prg without connection to arp module
256  
257 EOF
258     print "\n" ;
262 #===  FUNCTION  ================================================================
263 #         NAME:  read_configfile
264 #   PARAMETERS:  cfg_file - string -
265 #      RETURNS:  nothing
266 #  DESCRIPTION:  read cfg_file and set variables
267 #===============================================================================
268 sub read_configfile {
269     my $cfg;
270     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
271         if( -r $cfg_file ) {
272             $cfg = Config::IniFiles->new( -file => $cfg_file );
273         } else {
274             print STDERR "Couldn't read config file!\n";
275         }
276     } else {
277         $cfg = Config::IniFiles->new() ;
278     }
279     foreach my $section (keys %cfg_defaults) {
280         foreach my $param (keys %{$cfg_defaults{ $section }}) {
281             my $pinfo = $cfg_defaults{ $section }{ $param };
282             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
283         }
284     }
288 #===  FUNCTION  ================================================================
289 #         NAME:  logging
290 #   PARAMETERS:  level - string - default 'info'
291 #                msg - string -
292 #                facility - string - default 'LOG_DAEMON'
293 #      RETURNS:  nothing
294 #  DESCRIPTION:  function for logging
295 #===============================================================================
296 sub daemon_log {
297     # log into log_file
298     my( $msg, $level ) = @_;
299     if(not defined $msg) { return }
300     if(not defined $level) { $level = 1 }
301     if(defined $log_file){
302         open(LOG_HANDLE, ">>$log_file");
303         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
304             print STDERR "cannot open $log_file: $!";
305             return }
306             chomp($msg);
307                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
308             if($level <= $verbose){
309                 my ($seconds, $minutes, $hours, $monthday, $month,
310                         $year, $weekday, $yearday, $sommertime) = localtime(time);
311                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
312                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
313                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
314                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
315                 $month = $monthnames[$month];
316                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
317                 $year+=1900;
318                 my $name = $prg;
320                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
321                 print LOG_HANDLE $log_msg;
322                 if( $foreground ) { 
323                     print STDERR $log_msg;
324                 }
325             }
326         close( LOG_HANDLE );
327     }
331 #===  FUNCTION  ================================================================
332 #         NAME:  check_cmdline_param
333 #   PARAMETERS:  nothing
334 #      RETURNS:  nothing
335 #  DESCRIPTION:  validates commandline parameter
336 #===============================================================================
337 sub check_cmdline_param () {
338     my $err_config;
339     my $err_counter = 0;
340         if(not defined($cfg_file)) {
341                 $cfg_file = "/etc/gosa-si/server.conf";
342                 if(! -r $cfg_file) {
343                         $err_config = "please specify a config file";
344                         $err_counter += 1;
345                 }
346     }
347     if( $err_counter > 0 ) {
348         &usage( "", 1 );
349         if( defined( $err_config)) { print STDERR "$err_config\n"}
350         print STDERR "\n";
351         exit( -1 );
352     }
356 #===  FUNCTION  ================================================================
357 #         NAME:  check_pid
358 #   PARAMETERS:  nothing
359 #      RETURNS:  nothing
360 #  DESCRIPTION:  handels pid processing
361 #===============================================================================
362 sub check_pid {
363     $pid = -1;
364     # Check, if we are already running
365     if( open(LOCK_FILE, "<$pid_file") ) {
366         $pid = <LOCK_FILE>;
367         if( defined $pid ) {
368             chomp( $pid );
369             if( -f "/proc/$pid/stat" ) {
370                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
371                 if( $stat ) {
372                                         daemon_log("ERROR: Already running",1);
373                     close( LOCK_FILE );
374                     exit -1;
375                 }
376             }
377         }
378         close( LOCK_FILE );
379         unlink( $pid_file );
380     }
382     # create a syslog msg if it is not to possible to open PID file
383     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
384         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
385         if (open(LOCK_FILE, '<', $pid_file)
386                 && ($pid = <LOCK_FILE>))
387         {
388             chomp($pid);
389             $msg .= "(PID $pid)\n";
390         } else {
391             $msg .= "(unable to read PID)\n";
392         }
393         if( ! ($foreground) ) {
394             openlog( $0, "cons,pid", "daemon" );
395             syslog( "warning", $msg );
396             closelog();
397         }
398         else {
399             print( STDERR " $msg " );
400         }
401         exit( -1 );
402     }
405 #===  FUNCTION  ================================================================
406 #         NAME:  import_modules
407 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
408 #                are stored
409 #      RETURNS:  nothing
410 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
411 #                state is on is imported by "require 'file';"
412 #===============================================================================
413 sub import_modules {
414     daemon_log(" ", 1);
416     if (not -e $modules_path) {
417         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
418     }
420     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
421     while (defined (my $file = readdir (DIR))) {
422         if (not $file =~ /(\S*?).pm$/) {
423             next;
424         }
425                 my $mod_name = $1;
427         if( $file =~ /ArpHandler.pm/ ) {
428             if( $no_arp > 0 ) {
429                 next;
430             }
431         }
432         
433         eval { require $file; };
434         if ($@) {
435             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
436             daemon_log("$@", 5);
437                 } else {
438                         my $info = eval($mod_name.'::get_module_info()');
439                         # Only load module if get_module_info() returns a non-null object
440                         if( $info ) {
441                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
442                                 $known_modules->{$mod_name} = $info;
443                                 daemon_log("INFO: module $mod_name loaded", 5);
444                         }
445                 }
446     }   
447     close (DIR);
451 #===  FUNCTION  ================================================================
452 #         NAME:  sig_int_handler
453 #   PARAMETERS:  signal - string - signal arose from system
454 #      RETURNS:  noting
455 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
456 #===============================================================================
457 sub sig_int_handler {
458     my ($signal) = @_;
460 #       if (defined($ldap_handle)) {
461 #               $ldap_handle->disconnect;
462 #       }
463     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
464     
466     daemon_log("shutting down gosa-si-server", 1);
467     system("kill `ps -C gosa-si-server -o pid=`");
469 $SIG{INT} = \&sig_int_handler;
472 sub check_key_and_xml_validity {
473     my ($crypted_msg, $module_key, $session_id) = @_;
474     my $msg;
475     my $msg_hash;
476     my $error_string;
477     eval{
478         $msg = &decrypt_msg($crypted_msg, $module_key);
480         if ($msg =~ /<xml>/i){
481             $msg =~ s/\s+/ /g;  # just for better daemon_log
482             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
483             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
485             ##############
486             # check header
487             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
488             my $header_l = $msg_hash->{'header'};
489             if( 1 > @{$header_l} ) { die 'empty header tag'; }
490             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
491             my $header = @{$header_l}[0];
492             if( 0 == length $header) { die 'empty string in header tag'; }
494             ##############
495             # check source
496             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
497             my $source_l = $msg_hash->{'source'};
498             if( 1 > @{$source_l} ) { die 'empty source tag'; }
499             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
500             my $source = @{$source_l}[0];
501             if( 0 == length $source) { die 'source error'; }
503             ##############
504             # check target
505             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
506             my $target_l = $msg_hash->{'target'};
507             if( 1 > @{$target_l} ) { die 'empty target tag'; }
508         }
509     };
510     if($@) {
511         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
512         $msg = undef;
513         $msg_hash = undef;
514     }
516     return ($msg, $msg_hash);
520 sub check_outgoing_xml_validity {
521     my ($msg) = @_;
523     my $msg_hash;
524     eval{
525         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
527         ##############
528         # check header
529         my $header_l = $msg_hash->{'header'};
530         if( 1 != @{$header_l} ) {
531             die 'no or more than one headers specified';
532         }
533         my $header = @{$header_l}[0];
534         if( 0 == length $header) {
535             die 'header has length 0';
536         }
538         ##############
539         # check source
540         my $source_l = $msg_hash->{'source'};
541         if( 1 != @{$source_l} ) {
542             die 'no or more than 1 sources specified';
543         }
544         my $source = @{$source_l}[0];
545         if( 0 == length $source) {
546             die 'source has length 0';
547         }
548         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
549                 $source =~ /^GOSA$/i ) {
550             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
551         }
552         
553         ##############
554         # check target  
555         my $target_l = $msg_hash->{'target'};
556         if( 0 == @{$target_l} ) {
557             die "no targets specified";
558         }
559         foreach my $target (@$target_l) {
560             if( 0 == length $target) {
561                 die "target has length 0";
562             }
563             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
564                     $target =~ /^GOSA$/i ||
565                     $target =~ /^\*$/ ||
566                     $target =~ /KNOWN_SERVER/i ||
567                     $target =~ /JOBDB/i ||
568                     $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 ){
569                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
570             }
571         }
572     };
573     if($@) {
574         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
575         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
576         $msg_hash = undef;
577     }
579     return ($msg_hash);
583 sub input_from_known_server {
584     my ($input, $remote_ip, $session_id) = @_ ;  
585     my ($msg, $msg_hash, $module);
587     my $sql_statement= "SELECT * FROM known_server";
588     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
590     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
591         my $host_name = $hit->{hostname};
592         if( not $host_name =~ "^$remote_ip") {
593             next;
594         }
595         my $host_key = $hit->{hostkey};
596         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
597         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
599         # check if module can open msg envelope with module key
600         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
601         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
602             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
603             daemon_log("$@", 8);
604             next;
605         }
606         else {
607             $msg = $tmp_msg;
608             $msg_hash = $tmp_msg_hash;
609             $module = "SIPackages";
610             last;
611         }
612     }
614     if( (!$msg) || (!$msg_hash) || (!$module) ) {
615         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
616     }
617   
618     return ($msg, $msg_hash, $module);
622 sub input_from_known_client {
623     my ($input, $remote_ip, $session_id) = @_ ;  
624     my ($msg, $msg_hash, $module);
626     my $sql_statement= "SELECT * FROM known_clients";
627     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
628     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
629         my $host_name = $hit->{hostname};
630         if( not $host_name =~ /^$remote_ip:\d*$/) {
631                 next;
632                 }
633         my $host_key = $hit->{hostkey};
634         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
635         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
637         # check if module can open msg envelope with module key
638         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
640         if( (!$msg) || (!$msg_hash) ) {
641             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
642             &daemon_log("$@", 8);
643             next;
644         }
645         else {
646             $module = "SIPackages";
647             last;
648         }
649     }
651     if( (!$msg) || (!$msg_hash) || (!$module) ) {
652         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
653     }
655     return ($msg, $msg_hash, $module);
659 sub input_from_unknown_host {
660     no strict "refs";
661     my ($input, $session_id) = @_ ;
662     my ($msg, $msg_hash, $module);
663     my $error_string;
664     
665         my %act_modules = %$known_modules;
667         while( my ($mod, $info) = each(%act_modules)) {
669         # check a key exists for this module
670         my $module_key = ${$mod."_key"};
671         if( not defined $module_key ) {
672             if( $mod eq 'ArpHandler' ) {
673                 next;
674             }
675             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
676             next;
677         }
678         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
680         # check if module can open msg envelope with module key
681         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
682         if( (not defined $msg) || (not defined $msg_hash) ) {
683             next;
684         }
685         else {
686             $module = $mod;
687             last;
688         }
689     }
691     if( (!$msg) || (!$msg_hash) || (!$module)) {
692         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
693     }
695     return ($msg, $msg_hash, $module);
699 sub create_ciphering {
700     my ($passwd) = @_;
701         if((!defined($passwd)) || length($passwd)==0) {
702                 $passwd = "";
703         }
704     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
705     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
706     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
707     $my_cipher->set_iv($iv);
708     return $my_cipher;
712 sub encrypt_msg {
713     my ($msg, $key) = @_;
714     my $my_cipher = &create_ciphering($key);
715     my $len;
716     {
717             use bytes;
718             $len= 16-length($msg)%16;
719     }
720     $msg = "\0"x($len).$msg;
721     $msg = $my_cipher->encrypt($msg);
722     chomp($msg = &encode_base64($msg));
723     # there are no newlines allowed inside msg
724     $msg=~ s/\n//g;
725     return $msg;
729 sub decrypt_msg {
731     my ($msg, $key) = @_ ;
732     $msg = &decode_base64($msg);
733     my $my_cipher = &create_ciphering($key);
734     $msg = $my_cipher->decrypt($msg); 
735     $msg =~ s/\0*//g;
736     return $msg;
740 sub get_encrypt_key {
741     my ($target) = @_ ;
742     my $encrypt_key;
743     my $error = 0;
745     # target can be in known_server
746     if( not defined $encrypt_key ) {
747         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
748         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
749         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
750             my $host_name = $hit->{hostname};
751             if( $host_name ne $target ) {
752                 next;
753             }
754             $encrypt_key = $hit->{hostkey};
755             last;
756         }
757     }
759     # target can be in known_client
760     if( not defined $encrypt_key ) {
761         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
762         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
763         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
764             my $host_name = $hit->{hostname};
765             if( $host_name ne $target ) {
766                 next;
767             }
768             $encrypt_key = $hit->{hostkey};
769             last;
770         }
771     }
773     return $encrypt_key;
777 #===  FUNCTION  ================================================================
778 #         NAME:  open_socket
779 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
780 #                [PeerPort] string necessary if port not appended by PeerAddr
781 #      RETURNS:  socket IO::Socket::INET
782 #  DESCRIPTION:  open a socket to PeerAddr
783 #===============================================================================
784 sub open_socket {
785     my ($PeerAddr, $PeerPort) = @_ ;
786     if(defined($PeerPort)){
787         $PeerAddr = $PeerAddr.":".$PeerPort;
788     }
789     my $socket;
790     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
791             Porto => "tcp",
792             Type => SOCK_STREAM,
793             Timeout => 5,
794             );
795     if(not defined $socket) {
796         return;
797     }
798 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
799     return $socket;
803 #===  FUNCTION  ================================================================
804 #         NAME:  get_ip 
805 #   PARAMETERS:  interface name (i.e. eth0)
806 #      RETURNS:  (ip address) 
807 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
808 #===============================================================================
809 sub get_ip {
810         my $ifreq= shift;
811         my $result= "";
812         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
813         my $proto= getprotobyname('ip');
815         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
816                 or die "socket: $!";
818         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
819                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
820                 my ($port, $addr) = sockaddr_in $sin;
821                 my $ip            = inet_ntoa $addr;
823                 if ($ip && length($ip) > 0) {
824                         $result = $ip;
825                 }
826         }
828         return $result;
832 sub get_local_ip_for_remote_ip {
833         my $remote_ip= shift;
834         my $result="0.0.0.0";
836         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
837                 if($remote_ip eq "127.0.0.1") {
838                         $result = "127.0.0.1";
839                 } else {
840                         my $PROC_NET_ROUTE= ('/proc/net/route');
842                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
843                                 or die "Could not open $PROC_NET_ROUTE";
845                         my @ifs = <PROC_NET_ROUTE>;
847                         close(PROC_NET_ROUTE);
849                         # Eat header line
850                         shift @ifs;
851                         chomp @ifs;
852                         foreach my $line(@ifs) {
853                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
854                                 my $destination;
855                                 my $mask;
856                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
857                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
858                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
859                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
860                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
861                                         # destination matches route, save mac and exit
862                                         $result= &get_ip($Iface);
863                                         last;
864                                 }
865                         }
866                 }
867         } else {
868                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
869         }
870         return $result;
874 sub send_msg_to_target {
875     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
876     my $error = 0;
877     my $header;
878     my $new_status;
879     my $act_status;
880     my ($sql_statement, $res);
881   
882     if( $msg_header ) {
883         $header = "'$msg_header'-";
884     } else {
885         $header = "";
886     }
888         # Patch the source ip
889         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
890                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
891                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
892         }
894     # encrypt xml msg
895     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
897     # opensocket
898     my $socket = &open_socket($address);
899     if( !$socket ) {
900         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
901         $error++;
902     }
903     
904     if( $error == 0 ) {
905         # send xml msg
906         print $socket $crypted_msg."\n";
908         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
909         #daemon_log("DEBUG: message:\n$msg", 9);
910         
911     }
913     # close socket in any case
914     if( $socket ) {
915         close $socket;
916     }
918     if( $error > 0 ) { $new_status = "down"; }
919     else { $new_status = $msg_header; }
922     # known_clients
923     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
924     $res = $known_clients_db->select_dbentry($sql_statement);
925     if( keys(%$res) > 0) {
926         $act_status = $res->{1}->{'status'};
927         if( $act_status eq "down" ) {
928             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
929             $res = $known_clients_db->del_dbentry($sql_statement);
930             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
931         } else { 
932             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
933             $res = $known_clients_db->update_dbentry($sql_statement);
934             if($new_status eq "down"){
935                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
936             } else {
937                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
938             }
939         }
940     }
942     # known_server
943     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
944     $res = $known_server_db->select_dbentry($sql_statement);
945     if( keys(%$res) > 0 ) {
946         $act_status = $res->{1}->{'status'};
947         if( $act_status eq "down" ) {
948             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
949             $res = $known_server_db->del_dbentry($sql_statement);
950             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
951         } 
952         else { 
953             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
954             $res = $known_server_db->update_dbentry($sql_statement);
955             if($new_status eq "down"){
956                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957             }
958             else {
959                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
960             }
961         }
962     }
963     return $error; 
967 sub update_jobdb_status_for_send_msgs {
968     my ($answer, $error) = @_;
969     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
970         my $jobdb_id = $1;
971             
972         # sending msg faild
973         if( $error ) {
974             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
975                 my $sql_statement = "UPDATE $job_queue_tn ".
976                     "SET status='error', result='can not deliver msg, please consult log file' ".
977                     "WHERE id=$jobdb_id";
978                 my $res = $job_db->update_dbentry($sql_statement);
979             }
981         # sending msg was successful
982         } else {
983             my $sql_statement = "UPDATE $job_queue_tn ".
984                 "SET status='done' ".
985                 "WHERE id=$jobdb_id AND status='processed'";
986             my $res = $job_db->update_dbentry($sql_statement);
987         }
988     }
991 sub _start {
992     my ($kernel) = $_[KERNEL];
993     &trigger_db_loop($kernel);
994     $global_kernel = $kernel;
995         $kernel->yield('create_fai_server_db', $fai_server_tn );
996         $kernel->yield('create_fai_release_db', $fai_release_tn );
997         $kernel->sig(USR1 => "sig_handler");
998         $kernel->sig(USR2 => "create_packages_list_db");
1001 sub sig_handler {
1002         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1003         daemon_log("0 INFO got signal '$signal'", 1); 
1004         $kernel->sig_handled();
1005         return;
1009 sub msg_to_decrypt {
1010     my ($session, $heap) = @_[SESSION, HEAP];
1011     my $session_id = $session->ID;
1012     my ($msg, $msg_hash, $module);
1013     my $error = 0;
1015     # hole neue msg aus @msgs_to_decrypt
1016     my $next_msg = shift @msgs_to_decrypt;
1017     
1018     # entschlüssle sie
1020     # msg is from a new client or gosa
1021     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1022     # msg is from a gosa-si-server or gosa-si-bus
1023     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1024         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1025     }
1026     # msg is from a gosa-si-client
1027     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1028         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1029     }
1030     # an error occurred
1031     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1032         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1033         # could not understand a msg from its server the client cause a re-registering process
1034         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);
1035         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1036         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1037         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1038             my $host_name = $hit->{'hostname'};
1039             my $host_key = $hit->{'hostkey'};
1040             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1041             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1042             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1043         }
1044         $error++;
1045     }
1046     
1047     # add message to incoming_db
1048     if( $error == 0) {
1049         my $header = @{$msg_hash->{'header'}}[0];
1050         my $target = @{$msg_hash->{'target'}}[0];
1051         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1052                 primkey=>[],
1053                 headertag=>$header,
1054                                 targettag=>$target,
1055                 xmlmessage=>$msg,
1056                 timestamp=>&get_time,
1057                 module=>$module,
1058                 } );
1059         if ($res != 0) {
1060                         # TODO ist das mit $! so ok???
1061             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1062         }
1063     }
1068 sub next_task {
1069     my ($session, $heap) = @_[SESSION, HEAP];
1070 #    if (keys( %{ $heap->{task} } ) < $max_children ) {
1071         my $task = POE::Wheel::Run->new(
1072                 Program => sub { process_task($session, $heap) },
1073                 StdioFilter => POE::Filter::Reference->new(),
1074                 StdoutEvent  => "task_result",
1075                 StderrEvent  => "task_debug",
1076                 CloseEvent   => "task_done",
1077                );
1079         $heap->{task}->{ $task->ID } = $task;
1080 #    }
1083 sub handle_task_result {
1084     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1085     my $client_answer = $result->{'answer'};
1086     if( $client_answer =~ s/session_id=(\d+)$// ) {
1087         my $session_id = $1;
1088         if( defined $session_id ) {
1089             my $session_reference = $kernel->ID_id_to_session($session_id);
1090             if( defined $session_reference ) {
1091                 $heap = $session_reference->get_heap();
1092             }
1093         }
1095         if(exists $heap->{'client'}) {
1096             $heap->{'client'}->put($client_answer);
1097         }
1098     }
1099     $kernel->sig(CHLD => "child_reap");
1102 sub handle_task_debug {
1103     my $result = $_[ARG0];
1104     print STDERR "$result\n";
1107 sub handle_task_done {
1108     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1109     delete $heap->{task}->{$task_id};
1112 sub process_task {
1113     no strict "refs";
1114     my ($session, $heap, $input) = @_;
1115     my $session_id = $session->ID;
1116     my $error = 0;
1117     my $answer_l;
1118     my ($answer_header, @answer_target_l, $answer_source);
1119     my $client_answer = "";
1121     daemon_log("", 5); 
1122     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1123     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1125         ##################################################
1126         # fetch first unprocessed message from incoming_db
1127     # sometimes the program is faster than sqlite, so wait until informations are present at db
1128     my $id_sql;
1129     my $id_res;
1130     my $message_id;
1131 # TODO : das hier ist sehr sehr unschön        
1132     while (1) {
1133         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1134         $id_res = $incoming_db->exec_statement($id_sql);
1135         $message_id = @{@$id_res[0]}[0];
1136         if (defined $message_id) { last }
1137     }
1139     # fetch new message from incoming_db
1140     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1141     my $res = $incoming_db->exec_statement($sql);
1143     # prepare all variables needed to process message
1144     my $msg = @{@$res[0]}[3];
1145     my $incoming_id = @{@$res[0]}[0];
1146     my $module = @{@$res[0]}[4];
1147     my $header =  @{@$res[0]}[2];
1148     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1150     # messages which are an answer to a still running process should not be processed here
1151     if ($header =~ /^answer_(\d+)/) {
1152         return;
1153     }
1154    
1155     # delete message from db 
1156     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1157     my $delete_res = $incoming_db->exec_statement($delete_sql);
1159     ######################
1160     # process incoming msg
1161     if( $error == 0) {
1162         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1163                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1164         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1165         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1167         if ( 0 < @{$answer_l} ) {
1168             my $answer_str = join("\n", @{$answer_l});
1169             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1170         } else {
1171             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1172         }
1174     }
1175     if( !$answer_l ) { $error++ };
1177     ########
1178     # answer
1179     if( $error == 0 ) {
1181         foreach my $answer ( @{$answer_l} ) {
1182             # check outgoing msg to xml validity
1183             my $answer_hash = &check_outgoing_xml_validity($answer);
1184             if( not defined $answer_hash ) {
1185                 next;
1186             }
1187             
1188             $answer_header = @{$answer_hash->{'header'}}[0];
1189             @answer_target_l = @{$answer_hash->{'target'}};
1190             $answer_source = @{$answer_hash->{'source'}}[0];
1192             # deliver msg to all targets 
1193             foreach my $answer_target ( @answer_target_l ) {
1195                 # targets of msg are all gosa-si-clients in known_clients_db
1196                 if( $answer_target eq "*" ) {
1197                     # answer is for all clients
1198                     my $sql_statement= "SELECT * FROM known_clients";
1199                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1200                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1201                         my $host_name = $hit->{hostname};
1202                         my $host_key = $hit->{hostkey};
1203                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1204                         &update_jobdb_status_for_send_msgs($answer, $error);
1205                     }
1206                 }
1208                 # targets of msg are all gosa-si-server in known_server_db
1209                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1210                     # answer is for all server in known_server
1211                     my $sql_statement= "SELECT * FROM known_server";
1212                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1213                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1214                         my $host_name = $hit->{hostname};
1215                         my $host_key = $hit->{hostkey};
1216                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1217                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1218                         &update_jobdb_status_for_send_msgs($answer, $error);
1219                     }
1220                 }
1222                 # target of msg is GOsa
1223                                 elsif( $answer_target eq "GOSA" ) {
1224                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1225                                         my $add_on = "";
1226                     if( defined $session_id ) {
1227                         $add_on = ".session_id=$session_id";
1228                     }
1229                     # answer is for GOSA and has to returned to connected client
1230                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1231                     $client_answer = $gosa_answer.$add_on;
1232                 }
1234                 # target of msg is job queue at this host
1235                 elsif( $answer_target eq "JOBDB") {
1236                     $answer =~ /<header>(\S+)<\/header>/;   
1237                     my $header;
1238                     if( defined $1 ) { $header = $1; }
1239                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1240                     &update_jobdb_status_for_send_msgs($answer, $error);
1241                 }
1243                 # target of msg is a mac address
1244                 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 ) {
1245                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1246                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1247                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1248                     my $found_ip_flag = 0;
1249                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1250                         my $host_name = $hit->{hostname};
1251                         my $host_key = $hit->{hostkey};
1252                         $answer =~ s/$answer_target/$host_name/g;
1253                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1254                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1255                         &update_jobdb_status_for_send_msgs($answer, $error);
1256                         $found_ip_flag++ ;
1257                     }   
1258                     if( $found_ip_flag == 0) {
1259                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1260                         if( $bus_activ eq "true" ) { 
1261                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1262                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1263                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1264                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1265                                 my $bus_address = $hit->{hostname};
1266                                 my $bus_key = $hit->{hostkey};
1267                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1268                                 &update_jobdb_status_for_send_msgs($answer, $error);
1269                                 last;
1270                             }
1271                         }
1273                     }
1275                 #  answer is for one specific host   
1276                 } else {
1277                     # get encrypt_key
1278                     my $encrypt_key = &get_encrypt_key($answer_target);
1279                     if( not defined $encrypt_key ) {
1280                         # unknown target, forward msg to bus
1281                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1282                         if( $bus_activ eq "true" ) { 
1283                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1284                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1285                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1286                             my $res_length = keys( %{$query_res} );
1287                             if( $res_length == 0 ){
1288                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1289                                         "no bus found in known_server", 3);
1290                             }
1291                             else {
1292                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1293                                     my $bus_key = $hit->{hostkey};
1294                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1295                                     &update_jobdb_status_for_send_msgs($answer, $error);
1296                                 }
1297                             }
1298                         }
1299                         next;
1300                     }
1301                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1302                     &update_jobdb_status_for_send_msgs($answer, $error);
1303                 }
1304             }
1305         }
1306     }
1308     my $filter = POE::Filter::Reference->new();
1309     my %result = ( 
1310             status => "seems ok to me",
1311             answer => $client_answer,
1312             );
1314     my $output = $filter->put( [ \%result ] );
1315     print @$output;
1321 sub trigger_db_loop {
1322         my ($kernel) = @_ ;
1323         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1324         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1325         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1326     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1327         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1331 sub watch_for_done_jobs {
1332     my ($kernel,$heap) = @_[KERNEL, HEAP];
1334     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1335         " WHERE status='done'";
1336         my $res = $job_db->select_dbentry( $sql_statement );
1338     while( my ($id, $hit) = each %{$res} ) {
1339         my $jobdb_id = $hit->{id};
1340         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1341         my $res = $job_db->del_dbentry($sql_statement); 
1342     }
1344     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1348 sub watch_for_new_jobs {
1349         if($watch_for_new_jobs_in_progress == 0) {
1350                 $watch_for_new_jobs_in_progress = 1;
1351                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1353                 # check gosa job queue for jobs with executable timestamp
1354                 my $timestamp = &get_time();
1355                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1356                 my $res = $job_db->exec_statement( $sql_statement );
1358                 # Merge all new jobs that would do the same actions
1359                 my @drops;
1360                 my $hits;
1361                 foreach my $hit (reverse @{$res} ) {
1362                         my $macaddress= lc @{$hit}[8];
1363                         my $headertag= @{$hit}[5];
1364                         if(
1365                                 defined($hits->{$macaddress}) &&
1366                                 defined($hits->{$macaddress}->{$headertag}) &&
1367                                 defined($hits->{$macaddress}->{$headertag}[0])
1368                         ) {
1369                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1370                         }
1371                         $hits->{$macaddress}->{$headertag}= $hit;
1372                 }
1374                 # Delete new jobs with a matching job in state 'processing'
1375                 foreach my $macaddress (keys %{$hits}) {
1376                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1377                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1378                                 if(defined($jobdb_id)) {
1379                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1380                                         my $res = $job_db->exec_statement( $sql_statement );
1381                                         foreach my $hit (@{$res}) {
1382                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1383                                         }
1384                                 } else {
1385                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1386                                 }
1387                         }
1388                 }
1390                 # Commit deletion
1391                 $job_db->exec_statementlist(\@drops);
1393                 # Look for new jobs that could be executed
1394                 foreach my $macaddress (keys %{$hits}) {
1396                         # Look if there is an executing job
1397                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1398                         my $res = $job_db->exec_statement( $sql_statement );
1400                         # Skip new jobs for host if there is a processing job
1401                         if(defined($res) and defined @{$res}[0]) {
1402                                 next;
1403                         }
1405                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1406                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1407                                 if(defined($jobdb_id)) {
1408                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1410                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1411                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1412                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1414                                         # expect macaddress is unique!!!!!!
1415                                         my $target = $res_hash->{1}->{hostname};
1417                                         # change header
1418                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1420                                         # add sqlite_id
1421                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1423                                         $job_msg =~ /<header>(\S+)<\/header>/;
1424                                         my $header = $1 ;
1425                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1427                                         # update status in job queue to 'processing'
1428                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1429                                         my $res = $job_db->update_dbentry($sql_statement);
1430 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1432                                         # We don't want parallel processing
1433                                         last;
1434                                 }
1435                         }
1436                 }
1438                 $watch_for_new_jobs_in_progress = 0;
1439                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1440         }
1444 sub watch_for_new_messages {
1445     my ($kernel,$heap) = @_[KERNEL, HEAP];
1446     my @coll_user_msg;   # collection list of outgoing messages
1447     
1448     # check messaging_db for new incoming messages with executable timestamp
1449     my $timestamp = &get_time();
1450     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1451     my $res = $messaging_db->exec_statement( $sql_statement );
1452         foreach my $hit (@{$res}) {
1454         # create outgoing messages
1455         my $message_to = @{$hit}[3];
1456         # translate message_to to plain login name
1457         my @message_to_l = split(/,/, $message_to);  
1458                 my %receiver_h; 
1459                 foreach my $receiver (@message_to_l) {
1460                         if ($receiver =~ /^u_([\s\S]*)$/) {
1461                                 $receiver_h{$1} = 0;
1462                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1463 # TODO implement receiver translation
1464                                 # fetch all group members from ldap
1465                                 my $ldap_handle = &get_ldap_handle($session_id);
1466                                 my $mesg = $ldap_handle->search(
1467                                                                 base => $ldap_base,
1468                                                                 scope => 'sub',
1469                                                                 attrs => ['memberUid'],
1470                                                                 filter => "cn=$1"; 
1471                                                                 );
1472                                 if ($mesg->count) {
1473                                         my @entries = $mesg->entries;
1474                                         foreach my $entry (@entries) {
1475                                                         my @receivers= $entry->get_value("memberUid");
1476                                                         foreach my $receiver (@receivers) { 
1477                                                                         $receiver_h{$1} = 0;
1478                                                         }
1479                                         }
1480                                 }
1481 #         if ($mesg->count) {
1482 #                 my @entries = $mesg->entries;
1483 #                 foreach my $entry (@entries) {
1484 #                         # Only modify entry if it is not set to '$state'
1485 #                         if ($entry->get_value("FAIstate") ne "$state"){
1486 #                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1487 #                                 my $result;
1488 #                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1489 #                                 if (exists $tmp{'FAIobject'}){
1490 #                                         if ($state eq ''){
1491 #                                                 $result= $ldap_handle->modify($entry->dn, changes => [
1492 #                                                         delete => [ FAIstate => [] ] ]);
1493 #                                         } else {
1494 #                                                 $result= $ldap_handle->modify($entry->dn, changes => [
1495 #                                                         replace => [ FAIstate => $state ] ]);
1496 #                                         }
1497 #                                 } elsif ($state ne ''){
1498 #                                         $result= $ldap_handle->modify($entry->dn, changes => [
1499 #                                                 add     => [ objectClass => 'FAIobject' ],
1500 #                                                 add     => [ FAIstate => $state ] ]);
1501 #                                 }
1503 #                                 # Errors?
1504 #                                 if ($result->code){
1505 #                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1506 #                                 }
1507 #                         } else {
1508 #                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1509 #                         }  
1510 #                 }
1511 #         }
1512 #    # if no ldap handle defined
1513 #    } else {
1514 #        daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1515 #    }
1518                                 # add each member to receiver hash
1520                         } else {
1521                                 my $sbjct = &encode_base64(@{$hit}[1]);
1522                                 my $msg = &encode_base64(@{$hit}[7]);
1523                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message 'sbjct - msg'", 3); 
1524                         }
1525                 }
1526                 my @receiver_l = keys(%receiver_h);
1528         my $message_id = @{$hit}[0];
1530         #add each outgoing msg to messaging_db
1531         my $receiver;
1532         foreach $receiver (@receiver_l) {
1533             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1534                 "VALUES ('".
1535                 $message_id."', '".    # id
1536                 @{$hit}[1]."', '".     # subject
1537                 @{$hit}[2]."', '".     # message_from
1538                 $receiver."', '".      # message_to
1539                 "none"."', '".         # flag
1540                 "out"."', '".          # direction
1541                 @{$hit}[6]."', '".     # delivery_time
1542                 @{$hit}[7]."', '".     # message
1543                 $timestamp."'".     # timestamp
1544                 ")";
1545             &daemon_log("M DEBUG: $sql_statement", 1);
1546             my $res = $messaging_db->exec_statement($sql_statement);
1547             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1548         }
1550         # set incoming message to flag d=deliverd
1551         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1552         &daemon_log("M DEBUG: $sql_statement", 7);
1553         $res = $messaging_db->update_dbentry($sql_statement);
1554         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1555     }
1557     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1558     return;
1561 sub watch_for_delivery_messages {
1562     my ($kernel, $heap) = @_[KERNEL, HEAP];
1564     # select outgoing messages
1565     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1566     #&daemon_log("0 DEBUG: $sql", 7);
1567     my $res = $messaging_db->exec_statement( $sql_statement );
1568     
1569     # build out msg for each    usr
1570     foreach my $hit (@{$res}) {
1571         my $receiver = @{$hit}[3];
1572         my $msg_id = @{$hit}[0];
1573         my $subject = @{$hit}[1];
1574         my $message = @{$hit}[7];
1576         # resolve usr -> host where usr is logged in
1577         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1578         #&daemon_log("0 DEBUG: $sql", 7);
1579         my $res = $login_users_db->exec_statement($sql);
1581         # reciver is logged in nowhere
1582         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1584                 my $send_succeed = 0;
1585                 foreach my $hit (@$res) {
1586                                 my $receiver_host = @$hit[0];
1587                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1589                                 # fetch key to encrypt msg propperly for usr/host
1590                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1591                                 &daemon_log("0 DEBUG: $sql", 7);
1592                                 my $res = $known_clients_db->exec_statement($sql);
1594                                 # host is already down
1595                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1597                                 # host is on
1598                                 my $receiver_key = @{@{$res}[0]}[2];
1599                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1600                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1601                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1602                                 if ($error == 0 ) {
1603                                         $send_succeed++ ;
1604                                 }
1605                 }
1607                 if ($send_succeed) {
1608                                 # set outgoing msg at db to deliverd
1609                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1610                                 &daemon_log("0 DEBUG: $sql", 7);
1611                                 my $res = $messaging_db->exec_statement($sql); 
1612                 }
1613         }
1615     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1616     return;
1620 sub watch_for_done_messages {
1621     my ($kernel,$heap) = @_[KERNEL, HEAP];
1623     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1624     #&daemon_log("0 DEBUG: $sql", 7);
1625     my $res = $messaging_db->exec_statement($sql); 
1627     foreach my $hit (@{$res}) {
1628         my $msg_id = @{$hit}[0];
1630         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1631         #&daemon_log("0 DEBUG: $sql", 7); 
1632         my $res = $messaging_db->exec_statement($sql);
1634         # not all usr msgs have been seen till now
1635         if ( ref(@$res[0]) eq "ARRAY") { next; }
1636         
1637         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1638         #&daemon_log("0 DEBUG: $sql", 7);
1639         $res = $messaging_db->exec_statement($sql);
1640     
1641     }
1643     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1644     return;
1648 sub get_ldap_handle {
1649         my ($session_id) = @_;
1650         my $heap;
1651         my $ldap_handle;
1653         if (not defined $session_id ) { $session_id = 0 };
1654         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1656         if ($session_id == 0) {
1657                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1658                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1659                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1661         } else {
1662                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1663                 if( defined $session_reference ) {
1664                         $heap = $session_reference->get_heap();
1665                 }
1667                 if (not defined $heap) {
1668                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1669                         return;
1670                 }
1672                 # TODO: This "if" is nonsense, because it doesn't prove that the
1673                 #       used handle is still valid - or if we've to reconnect...
1674                 #if (not exists $heap->{ldap_handle}) {
1675                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1676                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1677                         $heap->{ldap_handle} = $ldap_handle;
1678                 #}
1679         }
1680         return $ldap_handle;
1684 sub change_fai_state {
1685     my ($st, $targets, $session_id) = @_;
1686     $session_id = 0 if not defined $session_id;
1687     # Set FAI state to localboot
1688     my %mapActions= (
1689         reboot    => '',
1690         update    => 'softupdate',
1691         localboot => 'localboot',
1692         reinstall => 'install',
1693         rescan    => '',
1694         wake      => '',
1695         memcheck  => 'memcheck',
1696         sysinfo   => 'sysinfo',
1697         install   => 'install',
1698     );
1700     # Return if this is unknown
1701     if (!exists $mapActions{ $st }){
1702         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1703       return;
1704     }
1706     my $state= $mapActions{ $st };
1708     my $ldap_handle = &get_ldap_handle($session_id);
1709     if( defined($ldap_handle) ) {
1711       # Build search filter for hosts
1712         my $search= "(&(objectClass=GOhard)";
1713         foreach (@{$targets}){
1714             $search.= "(macAddress=$_)";
1715         }
1716         $search.= ")";
1718       # If there's any host inside of the search string, procress them
1719         if (!($search =~ /macAddress/)){
1720             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1721             return;
1722         }
1724       # Perform search for Unit Tag
1725       my $mesg = $ldap_handle->search(
1726           base   => $ldap_base,
1727           scope  => 'sub',
1728           attrs  => ['dn', 'FAIstate', 'objectClass'],
1729           filter => "$search"
1730           );
1732           if ($mesg->count) {
1733                   my @entries = $mesg->entries;
1734                   foreach my $entry (@entries) {
1735                           # Only modify entry if it is not set to '$state'
1736                           if ($entry->get_value("FAIstate") ne "$state"){
1737                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1738                                   my $result;
1739                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1740                                   if (exists $tmp{'FAIobject'}){
1741                                           if ($state eq ''){
1742                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1743                                                           delete => [ FAIstate => [] ] ]);
1744                                           } else {
1745                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1746                                                           replace => [ FAIstate => $state ] ]);
1747                                           }
1748                                   } elsif ($state ne ''){
1749                                           $result= $ldap_handle->modify($entry->dn, changes => [
1750                                                   add     => [ objectClass => 'FAIobject' ],
1751                                                   add     => [ FAIstate => $state ] ]);
1752                                   }
1754                                   # Errors?
1755                                   if ($result->code){
1756                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1757                                   }
1758                           } else {
1759                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1760                           }  
1761                   }
1762           }
1763     # if no ldap handle defined
1764     } else {
1765         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1766     }
1771 sub change_goto_state {
1772     my ($st, $targets, $session_id) = @_;
1773     $session_id = 0  if not defined $session_id;
1775     # Switch on or off?
1776     my $state= $st eq 'active' ? 'active': 'locked';
1778     my $ldap_handle = &get_ldap_handle($session_id);
1779     if( defined($ldap_handle) ) {
1781       # Build search filter for hosts
1782       my $search= "(&(objectClass=GOhard)";
1783       foreach (@{$targets}){
1784         $search.= "(macAddress=$_)";
1785       }
1786       $search.= ")";
1788       # If there's any host inside of the search string, procress them
1789       if (!($search =~ /macAddress/)){
1790         return;
1791       }
1793       # Perform search for Unit Tag
1794       my $mesg = $ldap_handle->search(
1795           base   => $ldap_base,
1796           scope  => 'sub',
1797           attrs  => ['dn', 'gotoMode'],
1798           filter => "$search"
1799           );
1801       if ($mesg->count) {
1802         my @entries = $mesg->entries;
1803         foreach my $entry (@entries) {
1805           # Only modify entry if it is not set to '$state'
1806           if ($entry->get_value("gotoMode") ne $state){
1808             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1809             my $result;
1810             $result= $ldap_handle->modify($entry->dn, changes => [
1811                                                 replace => [ gotoMode => $state ] ]);
1813             # Errors?
1814             if ($result->code){
1815               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1816             }
1818           }
1819         }
1820       }
1822     }
1826 sub run_create_fai_server_db {
1827     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1828     my $session_id = $session->ID;
1829     my $task = POE::Wheel::Run->new(
1830             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1831             StdoutEvent  => "session_run_result",
1832             StderrEvent  => "session_run_debug",
1833             CloseEvent   => "session_run_done",
1834             );
1836     $heap->{task}->{ $task->ID } = $task;
1837     return;
1841 sub create_fai_server_db {
1842     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1843         my $result;
1845         if (not defined $session_id) { $session_id = 0; }
1846     my $ldap_handle = &get_ldap_handle();
1847         if(defined($ldap_handle)) {
1848                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1849                 my $mesg= $ldap_handle->search(
1850                         base   => $ldap_base,
1851                         scope  => 'sub',
1852                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1853                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1854                 );
1855                 if($mesg->{'resultCode'} == 0 &&
1856                    $mesg->count != 0) {
1857                    foreach my $entry (@{$mesg->{entries}}) {
1858                            if($entry->exists('FAIrepository')) {
1859                                    # Add an entry for each Repository configured for server
1860                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1861                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1862                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1863                                                    $result= $fai_server_db->add_dbentry( { 
1864                                                                    table => $table_name,
1865                                                                    primkey => ['server', 'release', 'tag'],
1866                                                                    server => $tmp_url,
1867                                                                    release => $tmp_release,
1868                                                                    sections => $tmp_sections,
1869                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1870                                                            } );
1871                                            }
1872                                    }
1873                            }
1874                    }
1875                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1877                 # TODO: Find a way to post the 'create_packages_list_db' event
1878                 if(not defined($dont_create_packages_list)) {
1879                         &create_packages_list_db(undef, undef, $session_id);
1880                 }
1881         }       
1882     
1883     $ldap_handle->disconnect;
1884         return $result;
1888 sub run_create_fai_release_db {
1889     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1890         my $session_id = $session->ID;
1891     my $task = POE::Wheel::Run->new(
1892             Program => sub { &create_fai_release_db($table_name, $session_id) },
1893             StdoutEvent  => "session_run_result",
1894             StderrEvent  => "session_run_debug",
1895             CloseEvent   => "session_run_done",
1896             );
1898     $heap->{task}->{ $task->ID } = $task;
1899     return;
1903 sub create_fai_release_db {
1904         my ($table_name, $session_id) = @_;
1905         my $result;
1907     # used for logging
1908     if (not defined $session_id) { $session_id = 0; }
1910     my $ldap_handle = &get_ldap_handle();
1911         if(defined($ldap_handle)) {
1912                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1913                 my $mesg= $ldap_handle->search(
1914                         base   => $ldap_base,
1915                         scope  => 'sub',
1916                         attrs  => [],
1917                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1918                 );
1919                 if($mesg->{'resultCode'} == 0 &&
1920                         $mesg->count != 0) {
1921                         # Walk through all possible FAI container ou's
1922                         my @sql_list;
1923                         my $timestamp= &get_time();
1924                         foreach my $ou (@{$mesg->{entries}}) {
1925                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1926                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1927                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1928                                         if(@tmp_array) {
1929                                                 foreach my $entry (@tmp_array) {
1930                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1931                                                                 my $sql= 
1932                                                                 "INSERT INTO $table_name "
1933                                                                 ."(timestamp, release, class, type, state) VALUES ("
1934                                                                 .$timestamp.","
1935                                                                 ."'".$entry->{'release'}."',"
1936                                                                 ."'".$entry->{'class'}."',"
1937                                                                 ."'".$entry->{'type'}."',"
1938                                                                 ."'".$entry->{'state'}."')";
1939                                                                 push @sql_list, $sql;
1940                                                         }
1941                                                 }
1942                                         }
1943                                 }
1944                         }
1946                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1947                         if(@sql_list) {
1948                                 unshift @sql_list, "VACUUM";
1949                                 unshift @sql_list, "DELETE FROM $table_name";
1950                                 $fai_release_db->exec_statementlist(\@sql_list);
1951                         }
1952                         daemon_log("$session_id DEBUG: Done with inserting",7);
1953                 }
1954                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1955         }
1956     $ldap_handle->disconnect;
1957         return $result;
1960 sub get_fai_types {
1961         my $tmp_classes = shift || return undef;
1962         my @result;
1964         foreach my $type(keys %{$tmp_classes}) {
1965                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1966                         my $entry = {
1967                                 type => $type,
1968                                 state => $tmp_classes->{$type}[0],
1969                         };
1970                         push @result, $entry;
1971                 }
1972         }
1974         return @result;
1977 sub get_fai_state {
1978         my $result = "";
1979         my $tmp_classes = shift || return $result;
1981         foreach my $type(keys %{$tmp_classes}) {
1982                 if(defined($tmp_classes->{$type}[0])) {
1983                         $result = $tmp_classes->{$type}[0];
1984                         
1985                 # State is equal for all types in class
1986                         last;
1987                 }
1988         }
1990         return $result;
1993 sub resolve_fai_classes {
1994         my ($fai_base, $ldap_handle, $session_id) = @_;
1995         if (not defined $session_id) { $session_id = 0; }
1996         my $result;
1997         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1998         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1999         my $fai_classes;
2001         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2002         my $mesg= $ldap_handle->search(
2003                 base   => $fai_base,
2004                 scope  => 'sub',
2005                 attrs  => ['cn','objectClass','FAIstate'],
2006                 filter => $fai_filter,
2007         );
2008         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2010         if($mesg->{'resultCode'} == 0 &&
2011                 $mesg->count != 0) {
2012                 foreach my $entry (@{$mesg->{entries}}) {
2013                         if($entry->exists('cn')) {
2014                                 my $tmp_dn= $entry->dn();
2016                                 # Skip classname and ou dn parts for class
2017                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2019                                 # Skip classes without releases
2020                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2021                                         next;
2022                                 }
2024                                 my $tmp_cn= $entry->get_value('cn');
2025                                 my $tmp_state= $entry->get_value('FAIstate');
2027                                 my $tmp_type;
2028                                 # Get FAI type
2029                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2030                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2031                                                 $tmp_type= $oclass;
2032                                                 last;
2033                                         }
2034                                 }
2036                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2037                                         # A Subrelease
2038                                         my @sub_releases = split(/,/, $tmp_release);
2040                                         # Walk through subreleases and build hash tree
2041                                         my $hash;
2042                                         while(my $tmp_sub_release = pop @sub_releases) {
2043                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2044                                         }
2045                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2046                                 } else {
2047                                         # A branch, no subrelease
2048                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2049                                 }
2050                         } elsif (!$entry->exists('cn')) {
2051                                 my $tmp_dn= $entry->dn();
2052                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2054                                 # Skip classes without releases
2055                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2056                                         next;
2057                                 }
2059                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2060                                         # A Subrelease
2061                                         my @sub_releases= split(/,/, $tmp_release);
2063                                         # Walk through subreleases and build hash tree
2064                                         my $hash;
2065                                         while(my $tmp_sub_release = pop @sub_releases) {
2066                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2067                                         }
2068                                         # Remove the last two characters
2069                                         chop($hash);
2070                                         chop($hash);
2072                                         eval('$fai_classes->'.$hash.'= {}');
2073                                 } else {
2074                                         # A branch, no subrelease
2075                                         if(!exists($fai_classes->{$tmp_release})) {
2076                                                 $fai_classes->{$tmp_release} = {};
2077                                         }
2078                                 }
2079                         }
2080                 }
2082                 # The hash is complete, now we can honor the copy-on-write based missing entries
2083                 foreach my $release (keys %$fai_classes) {
2084                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2085                 }
2086         }
2087         return $result;
2090 sub apply_fai_inheritance {
2091        my $fai_classes = shift || return {};
2092        my $tmp_classes;
2094        # Get the classes from the branch
2095        foreach my $class (keys %{$fai_classes}) {
2096                # Skip subreleases
2097                if($class =~ /^ou=.*$/) {
2098                        next;
2099                } else {
2100                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2101                }
2102        }
2104        # Apply to each subrelease
2105        foreach my $subrelease (keys %{$fai_classes}) {
2106                if($subrelease =~ /ou=/) {
2107                        foreach my $tmp_class (keys %{$tmp_classes}) {
2108                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2109                                        $fai_classes->{$subrelease}->{$tmp_class} =
2110                                        deep_copy($tmp_classes->{$tmp_class});
2111                                } else {
2112                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2113                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2114                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2115                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2116                                                }
2117                                        }
2118                                }
2119                        }
2120                }
2121        }
2123        # Find subreleases in deeper levels
2124        foreach my $subrelease (keys %{$fai_classes}) {
2125                if($subrelease =~ /ou=/) {
2126                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2127                                if($subsubrelease =~ /ou=/) {
2128                                        apply_fai_inheritance($fai_classes->{$subrelease});
2129                                }
2130                        }
2131                }
2132        }
2134        return $fai_classes;
2137 sub get_fai_release_entries {
2138         my $tmp_classes = shift || return;
2139         my $parent = shift || "";
2140         my @result = shift || ();
2142         foreach my $entry (keys %{$tmp_classes}) {
2143                 if(defined($entry)) {
2144                         if($entry =~ /^ou=.*$/) {
2145                                 my $release_name = $entry;
2146                                 $release_name =~ s/ou=//g;
2147                                 if(length($parent)>0) {
2148                                         $release_name = $parent."/".$release_name;
2149                                 }
2150                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2151                                 foreach my $bufentry(@bufentries) {
2152                                         push @result, $bufentry;
2153                                 }
2154                         } else {
2155                                 my @types = get_fai_types($tmp_classes->{$entry});
2156                                 foreach my $type (@types) {
2157                                         push @result, 
2158                                         {
2159                                                 'class' => $entry,
2160                                                 'type' => $type->{'type'},
2161                                                 'release' => $parent,
2162                                                 'state' => $type->{'state'},
2163                                         };
2164                                 }
2165                         }
2166                 }
2167         }
2169         return @result;
2172 sub deep_copy {
2173         my $this = shift;
2174         if (not ref $this) {
2175                 $this;
2176         } elsif (ref $this eq "ARRAY") {
2177                 [map deep_copy($_), @$this];
2178         } elsif (ref $this eq "HASH") {
2179                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2180         } else { die "what type is $_?" }
2184 sub session_run_result {
2185     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2186     $kernel->sig(CHLD => "child_reap");
2189 sub session_run_debug {
2190     my $result = $_[ARG0];
2191     print STDERR "$result\n";
2194 sub session_run_done {
2195     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2196     delete $heap->{task}->{$task_id};
2200 sub create_sources_list {
2201         my $session_id = shift;
2202         my $ldap_handle = &main::get_ldap_handle;
2203         my $result="/tmp/gosa_si_tmp_sources_list";
2205         # Remove old file
2206         if(stat($result)) {
2207                 unlink($result);
2208                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2209         }
2211         my $fh;
2212         open($fh, ">$result");
2213         if (not defined $fh) {
2214                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2215                 return undef;
2216         }
2217         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2218                 my $mesg=$ldap_handle->search(
2219                         base    => $main::ldap_server_dn,
2220                         scope   => 'base',
2221                         attrs   => 'FAIrepository',
2222                         filter  => 'objectClass=FAIrepositoryServer'
2223                 );
2224                 if($mesg->count) {
2225                         foreach my $entry(@{$mesg->{'entries'}}) {
2226                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2227                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2228                                         my $line = "deb $server $release";
2229                                         $sections =~ s/,/ /g;
2230                                         $line.= " $sections";
2231                                         print $fh $line."\n";
2232                                 }
2233                         }
2234                 }
2235         } else {
2236                 if (defined $main::ldap_server_dn){
2237                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2238                 } else {
2239                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2240                 }
2241         }
2242         close($fh);
2244         return $result;
2248 sub run_create_packages_list_db {
2249     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2250         my $session_id = $session->ID;
2252         my $task = POE::Wheel::Run->new(
2253                                         Priority => +20,
2254                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2255                                         StdoutEvent  => "session_run_result",
2256                                         StderrEvent  => "session_run_debug",
2257                                         CloseEvent   => "session_run_done",
2258                                         );
2259         $heap->{task}->{ $task->ID } = $task;
2263 sub create_packages_list_db {
2264         my ($ldap_handle, $sources_file, $session_id) = @_;
2265         
2266         # it should not be possible to trigger a recreation of packages_list_db
2267         # while packages_list_db is under construction, so set flag packages_list_under_construction
2268         # which is tested befor recreation can be started
2269         if (-r $packages_list_under_construction) {
2270                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2271                 return;
2272         } else {
2273                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2274                 # set packages_list_under_construction to true
2275                 system("touch $packages_list_under_construction");
2276                 @packages_list_statements=();
2277         }
2279         if (not defined $session_id) { $session_id = 0; }
2280         if (not defined $ldap_handle) { 
2281                 $ldap_handle= &get_ldap_handle();
2283                 if (not defined $ldap_handle) {
2284                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2285                         unlink($packages_list_under_construction);
2286                         return;
2287                 }
2288         }
2289         if (not defined $sources_file) { 
2290                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2291                 $sources_file = &create_sources_list($session_id);
2292         }
2294         if (not defined $sources_file) {
2295                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2296                 unlink($packages_list_under_construction);
2297                 return;
2298         }
2300         my $line;
2302         open(CONFIG, "<$sources_file") or do {
2303                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2304                 unlink($packages_list_under_construction);
2305                 return;
2306         };
2308         # Read lines
2309         while ($line = <CONFIG>){
2310                 # Unify
2311                 chop($line);
2312                 $line =~ s/^\s+//;
2313                 $line =~ s/^\s+/ /;
2315                 # Strip comments
2316                 $line =~ s/#.*$//g;
2318                 # Skip empty lines
2319                 if ($line =~ /^\s*$/){
2320                         next;
2321                 }
2323                 # Interpret deb line
2324                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2325                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2326                         my $section;
2327                         foreach $section (split(' ', $sections)){
2328                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2329                         }
2330                 }
2331         }
2333         close (CONFIG);
2335         find(\&cleanup_and_extract, keys( %repo_dirs ));
2336         &main::strip_packages_list_statements();
2337         unshift @packages_list_statements, "VACUUM";
2338         $packages_list_db->exec_statementlist(\@packages_list_statements);
2339         unlink($packages_list_under_construction);
2340         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2341         return;
2344 # This function should do some intensive task to minimize the db-traffic
2345 sub strip_packages_list_statements {
2346     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2347         my @new_statement_list=();
2348         my $hash;
2349         my $insert_hash;
2350         my $update_hash;
2351         my $delete_hash;
2352         my $local_timestamp=get_time();
2354         foreach my $existing_entry (@existing_entries) {
2355                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2356         }
2358         foreach my $statement (@packages_list_statements) {
2359                 if($statement =~ /^INSERT/i) {
2360                         # Assign the values from the insert statement
2361                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2362                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2363                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2364                                 # If section or description has changed, update the DB
2365                                 if( 
2366                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2367                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2368                                 ) {
2369                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2370                                 }
2371                         } else {
2372                                 # Insert a non-existing entry to db
2373                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2374                         }
2375                 } elsif ($statement =~ /^UPDATE/i) {
2376                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2377                         /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2378                         foreach my $distribution (keys %{$hash}) {
2379                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2380                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2381                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2382                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2383                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2384                                                 my $section;
2385                                                 my $description;
2386                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2387                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2388                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2389                                                 }
2390                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2391                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2392                                                 }
2393                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2394                                         }
2395                                 }
2396                         }
2397                 }
2398         }
2400         # TODO: Check for orphaned entries
2402         # unroll the insert_hash
2403         foreach my $distribution (keys %{$insert_hash}) {
2404                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2405                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2406                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2407                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2408                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2409                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2410                                 ."'$local_timestamp')";
2411                         }
2412                 }
2413         }
2415         # unroll the update hash
2416         foreach my $distribution (keys %{$update_hash}) {
2417                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2418                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2419                                 my $set = "";
2420                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2421                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2422                                 }
2423                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2424                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2425                                 }
2426                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2427                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2428                                 }
2429                                 if(defined($set) and length($set) > 0) {
2430                                         $set .= "timestamp = '$local_timestamp'";
2431                                 } else {
2432                                         next;
2433                                 }
2434                                 push @new_statement_list, 
2435                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2436                                         ." distribution = '$distribution'"
2437                                         ." AND package = '$package'"
2438                                         ." AND version = '$version'";
2439                         }
2440                 }
2441         }
2443         @packages_list_statements = @new_statement_list;
2447 sub parse_package_info {
2448     my ($baseurl, $dist, $section, $session_id)= @_;
2449     my ($package);
2450     if (not defined $session_id) { $session_id = 0; }
2451     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2452     $repo_dirs{ "${repo_path}/pool" } = 1;
2454     foreach $package ("Packages.gz"){
2455         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2456         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2457         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2458     }
2459     
2463 sub get_package {
2464     my ($url, $dest, $session_id)= @_;
2465     if (not defined $session_id) { $session_id = 0; }
2467     my $tpath = dirname($dest);
2468     -d "$tpath" || mkpath "$tpath";
2470     # This is ugly, but I've no time to take a look at "how it works in perl"
2471     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2472         system("gunzip -cd '$dest' > '$dest.in'");
2473         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2474         unlink($dest);
2475         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2476     } else {
2477         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2478     }
2479     return 0;
2483 sub parse_package {
2484     my ($path, $dist, $srv_path, $session_id)= @_;
2485     if (not defined $session_id) { $session_id = 0;}
2486     my ($package, $version, $section, $description);
2487     my $PACKAGES;
2488     my $timestamp = &get_time();
2490     if(not stat("$path.in")) {
2491         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2492         return;
2493     }
2495     open($PACKAGES, "<$path.in");
2496     if(not defined($PACKAGES)) {
2497         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2498         return;
2499     }
2501     # Read lines
2502     while (<$PACKAGES>){
2503         my $line = $_;
2504         # Unify
2505         chop($line);
2507         # Use empty lines as a trigger
2508         if ($line =~ /^\s*$/){
2509             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2510             push(@packages_list_statements, $sql);
2511             $package = "none";
2512             $version = "none";
2513             $section = "none";
2514             $description = "none"; 
2515             next;
2516         }
2518         # Trigger for package name
2519         if ($line =~ /^Package:\s/){
2520             ($package)= ($line =~ /^Package: (.*)$/);
2521             next;
2522         }
2524         # Trigger for version
2525         if ($line =~ /^Version:\s/){
2526             ($version)= ($line =~ /^Version: (.*)$/);
2527             next;
2528         }
2530         # Trigger for description
2531         if ($line =~ /^Description:\s/){
2532             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2533             next;
2534         }
2536         # Trigger for section
2537         if ($line =~ /^Section:\s/){
2538             ($section)= ($line =~ /^Section: (.*)$/);
2539             next;
2540         }
2542         # Trigger for filename
2543         if ($line =~ /^Filename:\s/){
2544             my ($filename) = ($line =~ /^Filename: (.*)$/);
2545             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2546             next;
2547         }
2548     }
2550     close( $PACKAGES );
2551     unlink( "$path.in" );
2552     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2556 sub store_fileinfo {
2557     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2559     my %fileinfo = (
2560         'package' => $package,
2561         'dist' => $dist,
2562         'version' => $vers,
2563     );
2565     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2569 sub cleanup_and_extract {
2570     my $fileinfo = $repo_files{ $File::Find::name };
2572     if( defined $fileinfo ) {
2574         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2575         my $sql;
2576         my $package = $fileinfo->{ 'package' };
2577         my $newver = $fileinfo->{ 'version' };
2579         mkpath($dir);
2580         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2582                 if( -f "$dir/DEBIAN/templates" ) {
2584                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2586                         my $tmpl= "";
2587                         {
2588                                 local $/=undef;
2589                                 open FILE, "$dir/DEBIAN/templates";
2590                                 $tmpl = &encode_base64(<FILE>);
2591                                 close FILE;
2592                         }
2593                         rmtree("$dir/DEBIAN/templates");
2595                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2596                 push @packages_list_statements, $sql;
2597                 }
2598     }
2600     return;
2604 #==== MAIN = main ==============================================================
2605 #  parse commandline options
2606 Getopt::Long::Configure( "bundling" );
2607 GetOptions("h|help" => \&usage,
2608         "c|config=s" => \$cfg_file,
2609         "f|foreground" => \$foreground,
2610         "v|verbose+" => \$verbose,
2611         "no-bus+" => \$no_bus,
2612         "no-arp+" => \$no_arp,
2613            );
2615 #  read and set config parameters
2616 &check_cmdline_param ;
2617 &read_configfile;
2618 &check_pid;
2620 $SIG{CHLD} = 'IGNORE';
2622 # forward error messages to logfile
2623 if( ! $foreground ) {
2624   open( STDIN,  '+>/dev/null' );
2625   open( STDOUT, '+>&STDIN'    );
2626   open( STDERR, '+>&STDIN'    );
2629 # Just fork, if we are not in foreground mode
2630 if( ! $foreground ) { 
2631     chdir '/'                 or die "Can't chdir to /: $!";
2632     $pid = fork;
2633     setsid                    or die "Can't start a new session: $!";
2634     umask 0;
2635 } else { 
2636     $pid = $$; 
2639 # Do something useful - put our PID into the pid_file
2640 if( 0 != $pid ) {
2641     open( LOCK_FILE, ">$pid_file" );
2642     print LOCK_FILE "$pid\n";
2643     close( LOCK_FILE );
2644     if( !$foreground ) { 
2645         exit( 0 ) 
2646     };
2649 # parse head url and revision from svn
2650 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2651 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2652 $server_headURL = defined $1 ? $1 : 'unknown' ;
2653 $server_revision = defined $2 ? $2 : 'unknown' ;
2654 if ($server_headURL =~ /\/tag\// || 
2655         $server_headURL =~ /\/branches\// ) {
2656     $server_status = "stable"; 
2657 } else {
2658     $server_status = "developmental" ;
2662 daemon_log(" ", 1);
2663 daemon_log("$0 started!", 1);
2664 daemon_log("status: $server_status", 1);
2665 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2667 if ($no_bus > 0) {
2668     $bus_activ = "false"
2671 # connect to incoming_db
2672 unlink($incoming_file_name);
2673 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2674 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2676 # connect to gosa-si job queue
2677 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2678 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2680 # connect to known_clients_db
2681 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2682 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2684 # connect to known_server_db
2685 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2686 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2688 # connect to login_usr_db
2689 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2690 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2692 # connect to fai_server_db and fai_release_db
2693 unlink($fai_server_file_name);
2694 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2695 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2697 unlink($fai_release_file_name);
2698 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2699 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2701 # connect to packages_list_db
2702 #unlink($packages_list_file_name);
2703 unlink($packages_list_under_construction);
2704 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2705 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2707 # connect to messaging_db
2708 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2709 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2712 # create xml object used for en/decrypting
2713 $xml = new XML::Simple();
2715 # create socket for incoming xml messages
2717 POE::Component::Server::TCP->new(
2718         Port => $server_port,
2719         ClientInput => sub {
2720         my ($kernel, $input) = @_[KERNEL, ARG0];
2721         push(@tasks, $input);
2722         push(@msgs_to_decrypt, $input);
2723         $kernel->yield("next_task");
2724         $kernel->yield("msg_to_decrypt");
2725         },
2726     InlineStates => {
2727         next_task => \&next_task,
2728         msg_to_decrypt => \&msg_to_decrypt,
2729         task_result => \&handle_task_result,
2730         task_done   => \&handle_task_done,
2731         task_debug  => \&handle_task_debug,
2732         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2733     }
2734 );
2736 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2738 # create session for repeatedly checking the job queue for jobs
2739 POE::Session->create(
2740         inline_states => {
2741                 _start => \&_start,
2742                 sig_handler => \&sig_handler,
2743         watch_for_new_messages => \&watch_for_new_messages,
2744         watch_for_delivery_messages => \&watch_for_delivery_messages,
2745         watch_for_done_messages => \&watch_for_done_messages,
2746                 watch_for_new_jobs => \&watch_for_new_jobs,
2747         watch_for_done_jobs => \&watch_for_done_jobs,
2748         create_packages_list_db => \&run_create_packages_list_db,
2749         create_fai_server_db => \&run_create_fai_server_db,
2750         create_fai_release_db => \&run_create_fai_release_db,
2751         session_run_result => \&session_run_result,
2752         session_run_debug => \&session_run_debug,
2753         session_run_done => \&session_run_done,
2754         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2755         }
2756 );
2759 # import all modules
2760 &import_modules;
2762 # check wether all modules are gosa-si valid passwd check
2764 POE::Kernel->run();
2765 exit;