Code

Added a couple of images
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5  qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
55 my $server_version = '$HeadURL$:$Rev$';
56 my $server_headURL;
57 my $server_revision;
58 my $server_status;
61 # TODO es gibt eine globale funktion get_ldap_handle
62 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
63 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
64 # - sessions die kein ldap handle brauchen, sollen auch keins haben
65 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
66 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_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
100 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_bus = 0;
113 $bus_activ = "true";
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 our $prg= basename($0);
121 # holds all gosa jobs
122 our $job_db;
123 our $job_queue_tn = 'jobs';
124 my $job_queue_file_name;
125 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
126                 "timestamp DEFAULT 'none'", 
127                 "status DEFAULT 'none'", 
128                 "result DEFAULT 'none'", 
129                 "progress DEFAULT 'none'", 
130         "headertag DEFAULT 'none'", 
131                 "targettag DEFAULT 'none'", 
132                 "xmlmessage DEFAULT 'none'", 
133                 "macaddress DEFAULT 'none'",
134                 "plainname DEFAULT 'none'",
135                 );
137 # holds all other gosa-sd as well as the gosa-sd-bus
138 our $known_server_db;
139 our $known_server_tn = "known_server";
140 my $known_server_file_name;
141 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
143 # holds all registrated clients
144 our $known_clients_db;
145 our $known_clients_tn = "known_clients";
146 my $known_clients_file_name;
147 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
149 # holds all logged in user at each client 
150 our $login_users_db;
151 our $login_users_tn = "login_users";
152 my $login_users_file_name;
153 my @login_users_col_names = ("client", "user", "timestamp");
155 # holds all fai server, the debian release and tag
156 our $fai_server_db;
157 our $fai_server_tn = "fai_server"; 
158 my $fai_server_file_name;
159 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
161 our $fai_release_db;
162 our $fai_release_tn = "fai_release"; 
163 my $fai_release_file_name;
164 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
166 # holds all packages available from different repositories
167 our $packages_list_db;
168 our $packages_list_tn = "packages_list";
169 my $packages_list_file_name;
170 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
171 my $outdir = "/tmp/packages_list_db";
172 my $arch = "i386"; 
174 # holds all messages which should be delivered to a user
175 our $messaging_db;
176 our $messaging_tn = "messaging"; 
177 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
178         "flag", "direction", "delivery_time", "message", "timestamp" );
179 my $messaging_file_name;
181 # path to directory to store client install log files
182 our $client_fai_log_dir = "/var/log/fai"; 
184 # queue which stores taskes until one of the $max_children children are ready to process the task
185 my @tasks = qw();
186 my $max_children = 2;
189 %cfg_defaults = (
190 "general" => {
191     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
192     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
193     },
194 "bus" => {
195     "activ" => [\$bus_activ, "true"],
196     },
197 "server" => {
198     "port" => [\$server_port, "20081"],
199     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
200     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
201     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
202     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
203     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
204     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
205     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
206     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
207     "repo-path" => [\$repo_path, '/srv/www/repository'],
208     "ldap-uri" => [\$ldap_uri, ""],
209     "ldap-base" => [\$ldap_base, ""],
210     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
211     "ldap-admin-password" => [\$ldap_admin_password, ""],
212     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
213     "max-clients" => [\$max_clients, 10],
214     },
215 "GOsaPackages" => {
216     "ip" => [\$gosa_ip, "0.0.0.0"],
217     "port" => [\$gosa_port, "20082"],
218     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
219     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
220     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
221     "key" => [\$GosaPackages_key, "none"],
222     },
223 "SIPackages" => {
224     "key" => [\$SIPackages_key, "none"],
225     },
226 );
229 #===  FUNCTION  ================================================================
230 #         NAME:  usage
231 #   PARAMETERS:  nothing
232 #      RETURNS:  nothing
233 #  DESCRIPTION:  print out usage text to STDERR
234 #===============================================================================
235 sub usage {
236     print STDERR << "EOF" ;
237 usage: $prg [-hvf] [-c config]
239            -h        : this (help) message
240            -c <file> : config file
241            -f        : foreground, process will not be forked to background
242            -v        : be verbose (multiple to increase verbosity)
243            -no-bus   : starts $prg without connection to bus
244            -no-arp   : starts $prg without connection to arp module
245  
246 EOF
247     print "\n" ;
251 #===  FUNCTION  ================================================================
252 #         NAME:  read_configfile
253 #   PARAMETERS:  cfg_file - string -
254 #      RETURNS:  nothing
255 #  DESCRIPTION:  read cfg_file and set variables
256 #===============================================================================
257 sub read_configfile {
258     my $cfg;
259     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
260         if( -r $cfg_file ) {
261             $cfg = Config::IniFiles->new( -file => $cfg_file );
262         } else {
263             print STDERR "Couldn't read config file!\n";
264         }
265     } else {
266         $cfg = Config::IniFiles->new() ;
267     }
268     foreach my $section (keys %cfg_defaults) {
269         foreach my $param (keys %{$cfg_defaults{ $section }}) {
270             my $pinfo = $cfg_defaults{ $section }{ $param };
271             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
272         }
273     }
277 #===  FUNCTION  ================================================================
278 #         NAME:  logging
279 #   PARAMETERS:  level - string - default 'info'
280 #                msg - string -
281 #                facility - string - default 'LOG_DAEMON'
282 #      RETURNS:  nothing
283 #  DESCRIPTION:  function for logging
284 #===============================================================================
285 sub daemon_log {
286     # log into log_file
287     my( $msg, $level ) = @_;
288     if(not defined $msg) { return }
289     if(not defined $level) { $level = 1 }
290     if(defined $log_file){
291         open(LOG_HANDLE, ">>$log_file");
292         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
293             print STDERR "cannot open $log_file: $!";
294             return }
295             chomp($msg);
296                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
297             if($level <= $verbose){
298                 my ($seconds, $minutes, $hours, $monthday, $month,
299                         $year, $weekday, $yearday, $sommertime) = localtime(time);
300                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
301                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
302                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
303                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
304                 $month = $monthnames[$month];
305                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
306                 $year+=1900;
307                 my $name = $prg;
309                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
310                 print LOG_HANDLE $log_msg;
311                 if( $foreground ) { 
312                     print STDERR $log_msg;
313                 }
314             }
315         close( LOG_HANDLE );
316     }
320 #===  FUNCTION  ================================================================
321 #         NAME:  check_cmdline_param
322 #   PARAMETERS:  nothing
323 #      RETURNS:  nothing
324 #  DESCRIPTION:  validates commandline parameter
325 #===============================================================================
326 sub check_cmdline_param () {
327     my $err_config;
328     my $err_counter = 0;
329         if(not defined($cfg_file)) {
330                 $cfg_file = "/etc/gosa-si/server.conf";
331                 if(! -r $cfg_file) {
332                         $err_config = "please specify a config file";
333                         $err_counter += 1;
334                 }
335     }
336     if( $err_counter > 0 ) {
337         &usage( "", 1 );
338         if( defined( $err_config)) { print STDERR "$err_config\n"}
339         print STDERR "\n";
340         exit( -1 );
341     }
345 #===  FUNCTION  ================================================================
346 #         NAME:  check_pid
347 #   PARAMETERS:  nothing
348 #      RETURNS:  nothing
349 #  DESCRIPTION:  handels pid processing
350 #===============================================================================
351 sub check_pid {
352     $pid = -1;
353     # Check, if we are already running
354     if( open(LOCK_FILE, "<$pid_file") ) {
355         $pid = <LOCK_FILE>;
356         if( defined $pid ) {
357             chomp( $pid );
358             if( -f "/proc/$pid/stat" ) {
359                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
360                 if( $stat ) {
361                                         daemon_log("ERROR: Already running",1);
362                     close( LOCK_FILE );
363                     exit -1;
364                 }
365             }
366         }
367         close( LOCK_FILE );
368         unlink( $pid_file );
369     }
371     # create a syslog msg if it is not to possible to open PID file
372     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
373         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
374         if (open(LOCK_FILE, '<', $pid_file)
375                 && ($pid = <LOCK_FILE>))
376         {
377             chomp($pid);
378             $msg .= "(PID $pid)\n";
379         } else {
380             $msg .= "(unable to read PID)\n";
381         }
382         if( ! ($foreground) ) {
383             openlog( $0, "cons,pid", "daemon" );
384             syslog( "warning", $msg );
385             closelog();
386         }
387         else {
388             print( STDERR " $msg " );
389         }
390         exit( -1 );
391     }
394 #===  FUNCTION  ================================================================
395 #         NAME:  import_modules
396 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
397 #                are stored
398 #      RETURNS:  nothing
399 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
400 #                state is on is imported by "require 'file';"
401 #===============================================================================
402 sub import_modules {
403     daemon_log(" ", 1);
405     if (not -e $modules_path) {
406         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
407     }
409     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
410     while (defined (my $file = readdir (DIR))) {
411         if (not $file =~ /(\S*?).pm$/) {
412             next;
413         }
414                 my $mod_name = $1;
416         if( $file =~ /ArpHandler.pm/ ) {
417             if( $no_arp > 0 ) {
418                 next;
419             }
420         }
421         
422         eval { require $file; };
423         if ($@) {
424             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
425             daemon_log("$@", 5);
426                 } else {
427                         my $info = eval($mod_name.'::get_module_info()');
428                         # Only load module if get_module_info() returns a non-null object
429                         if( $info ) {
430                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
431                                 $known_modules->{$mod_name} = $info;
432                                 daemon_log("INFO: module $mod_name loaded", 5);
433                         }
434                 }
435     }   
436     close (DIR);
440 #===  FUNCTION  ================================================================
441 #         NAME:  sig_int_handler
442 #   PARAMETERS:  signal - string - signal arose from system
443 #      RETURNS:  noting
444 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
445 #===============================================================================
446 sub sig_int_handler {
447     my ($signal) = @_;
449 #       if (defined($ldap_handle)) {
450 #               $ldap_handle->disconnect;
451 #       }
452     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
453     
455     daemon_log("shutting down gosa-si-server", 1);
456     system("kill `ps -C gosa-si-server -o pid=`");
458 $SIG{INT} = \&sig_int_handler;
461 sub check_key_and_xml_validity {
462     my ($crypted_msg, $module_key, $session_id) = @_;
463     my $msg;
464     my $msg_hash;
465     my $error_string;
466     eval{
467         $msg = &decrypt_msg($crypted_msg, $module_key);
469         if ($msg =~ /<xml>/i){
470             $msg =~ s/\s+/ /g;  # just for better daemon_log
471             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
472             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
474             ##############
475             # check header
476             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
477             my $header_l = $msg_hash->{'header'};
478             if( 1 > @{$header_l} ) { die 'empty header tag'; }
479             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
480             my $header = @{$header_l}[0];
481             if( 0 == length $header) { die 'empty string in header tag'; }
483             ##############
484             # check source
485             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
486             my $source_l = $msg_hash->{'source'};
487             if( 1 > @{$source_l} ) { die 'empty source tag'; }
488             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
489             my $source = @{$source_l}[0];
490             if( 0 == length $source) { die 'source error'; }
492             ##############
493             # check target
494             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
495             my $target_l = $msg_hash->{'target'};
496             if( 1 > @{$target_l} ) { die 'empty target tag'; }
497         }
498     };
499     if($@) {
500         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
501         $msg = undef;
502         $msg_hash = undef;
503     }
505     return ($msg, $msg_hash);
509 sub check_outgoing_xml_validity {
510     my ($msg) = @_;
512     my $msg_hash;
513     eval{
514         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
516         ##############
517         # check header
518         my $header_l = $msg_hash->{'header'};
519         if( 1 != @{$header_l} ) {
520             die 'no or more than one headers specified';
521         }
522         my $header = @{$header_l}[0];
523         if( 0 == length $header) {
524             die 'header has length 0';
525         }
527         ##############
528         # check source
529         my $source_l = $msg_hash->{'source'};
530         if( 1 != @{$source_l} ) {
531             die 'no or more than 1 sources specified';
532         }
533         my $source = @{$source_l}[0];
534         if( 0 == length $source) {
535             die 'source has length 0';
536         }
537         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
538                 $source =~ /^GOSA$/i ) {
539             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
540         }
541         
542         ##############
543         # check target  
544         my $target_l = $msg_hash->{'target'};
545         if( 0 == @{$target_l} ) {
546             die "no targets specified";
547         }
548         foreach my $target (@$target_l) {
549             if( 0 == length $target) {
550                 die "target has length 0";
551             }
552             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
553                     $target =~ /^GOSA$/i ||
554                     $target =~ /^\*$/ ||
555                     $target =~ /KNOWN_SERVER/i ||
556                     $target =~ /JOBDB/i ||
557                     $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 ){
558                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
559             }
560         }
561     };
562     if($@) {
563         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
564         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
565         $msg_hash = undef;
566     }
568     return ($msg_hash);
572 sub input_from_known_server {
573     my ($input, $remote_ip, $session_id) = @_ ;  
574     my ($msg, $msg_hash, $module);
576     my $sql_statement= "SELECT * FROM known_server";
577     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
579     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
580         my $host_name = $hit->{hostname};
581         if( not $host_name =~ "^$remote_ip") {
582             next;
583         }
584         my $host_key = $hit->{hostkey};
585         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
586         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
588         # check if module can open msg envelope with module key
589         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
590         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
591             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
592             daemon_log("$@", 8);
593             next;
594         }
595         else {
596             $msg = $tmp_msg;
597             $msg_hash = $tmp_msg_hash;
598             $module = "SIPackages";
599             last;
600         }
601     }
603     if( (!$msg) || (!$msg_hash) || (!$module) ) {
604         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
605     }
606   
607     return ($msg, $msg_hash, $module);
611 sub input_from_known_client {
612     my ($input, $remote_ip, $session_id) = @_ ;  
613     my ($msg, $msg_hash, $module);
615     my $sql_statement= "SELECT * FROM known_clients";
616     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
617     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
618         my $host_name = $hit->{hostname};
619         if( not $host_name =~ /^$remote_ip:\d*$/) {
620                 next;
621                 }
622         my $host_key = $hit->{hostkey};
623         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
624         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
626         # check if module can open msg envelope with module key
627         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
629         if( (!$msg) || (!$msg_hash) ) {
630             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
631             &daemon_log("$@", 8);
632             next;
633         }
634         else {
635             $module = "SIPackages";
636             last;
637         }
638     }
640     if( (!$msg) || (!$msg_hash) || (!$module) ) {
641         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
642     }
644     return ($msg, $msg_hash, $module);
648 sub input_from_unknown_host {
649     no strict "refs";
650     my ($input, $session_id) = @_ ;
651     my ($msg, $msg_hash, $module);
652     my $error_string;
653     
654         my %act_modules = %$known_modules;
656         while( my ($mod, $info) = each(%act_modules)) {
658         # check a key exists for this module
659         my $module_key = ${$mod."_key"};
660         if( not defined $module_key ) {
661             if( $mod eq 'ArpHandler' ) {
662                 next;
663             }
664             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
665             next;
666         }
667         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
669         # check if module can open msg envelope with module key
670         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
671         if( (not defined $msg) || (not defined $msg_hash) ) {
672             next;
673         }
674         else {
675             $module = $mod;
676             last;
677         }
678     }
680     if( (!$msg) || (!$msg_hash) || (!$module)) {
681         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
682     }
684     return ($msg, $msg_hash, $module);
688 sub create_ciphering {
689     my ($passwd) = @_;
690         if((!defined($passwd)) || length($passwd)==0) {
691                 $passwd = "";
692         }
693     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
694     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
695     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
696     $my_cipher->set_iv($iv);
697     return $my_cipher;
701 sub encrypt_msg {
702     my ($msg, $key) = @_;
703     my $my_cipher = &create_ciphering($key);
704     my $len;
705     {
706             use bytes;
707             $len= 16-length($msg)%16;
708     }
709     $msg = "\0"x($len).$msg;
710     $msg = $my_cipher->encrypt($msg);
711     chomp($msg = &encode_base64($msg));
712     # there are no newlines allowed inside msg
713     $msg=~ s/\n//g;
714     return $msg;
718 sub decrypt_msg {
720     my ($msg, $key) = @_ ;
721     $msg = &decode_base64($msg);
722     my $my_cipher = &create_ciphering($key);
723     $msg = $my_cipher->decrypt($msg); 
724     $msg =~ s/\0*//g;
725     return $msg;
729 sub get_encrypt_key {
730     my ($target) = @_ ;
731     my $encrypt_key;
732     my $error = 0;
734     # target can be in known_server
735     if( not defined $encrypt_key ) {
736         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
737         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
738         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
739             my $host_name = $hit->{hostname};
740             if( $host_name ne $target ) {
741                 next;
742             }
743             $encrypt_key = $hit->{hostkey};
744             last;
745         }
746     }
748     # target can be in known_client
749     if( not defined $encrypt_key ) {
750         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
751         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
752         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
753             my $host_name = $hit->{hostname};
754             if( $host_name ne $target ) {
755                 next;
756             }
757             $encrypt_key = $hit->{hostkey};
758             last;
759         }
760     }
762     return $encrypt_key;
766 #===  FUNCTION  ================================================================
767 #         NAME:  open_socket
768 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
769 #                [PeerPort] string necessary if port not appended by PeerAddr
770 #      RETURNS:  socket IO::Socket::INET
771 #  DESCRIPTION:  open a socket to PeerAddr
772 #===============================================================================
773 sub open_socket {
774     my ($PeerAddr, $PeerPort) = @_ ;
775     if(defined($PeerPort)){
776         $PeerAddr = $PeerAddr.":".$PeerPort;
777     }
778     my $socket;
779     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
780             Porto => "tcp",
781             Type => SOCK_STREAM,
782             Timeout => 5,
783             );
784     if(not defined $socket) {
785         return;
786     }
787 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
788     return $socket;
792 #===  FUNCTION  ================================================================
793 #         NAME:  get_ip 
794 #   PARAMETERS:  interface name (i.e. eth0)
795 #      RETURNS:  (ip address) 
796 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
797 #===============================================================================
798 sub get_ip {
799         my $ifreq= shift;
800         my $result= "";
801         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
802         my $proto= getprotobyname('ip');
804         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
805                 or die "socket: $!";
807         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
808                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
809                 my ($port, $addr) = sockaddr_in $sin;
810                 my $ip            = inet_ntoa $addr;
812                 if ($ip && length($ip) > 0) {
813                         $result = $ip;
814                 }
815         }
817         return $result;
821 sub get_local_ip_for_remote_ip {
822         my $remote_ip= shift;
823         my $result="0.0.0.0";
825         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
826                 if($remote_ip eq "127.0.0.1") {
827                         $result = "127.0.0.1";
828                 } else {
829                         my $PROC_NET_ROUTE= ('/proc/net/route');
831                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
832                                 or die "Could not open $PROC_NET_ROUTE";
834                         my @ifs = <PROC_NET_ROUTE>;
836                         close(PROC_NET_ROUTE);
838                         # Eat header line
839                         shift @ifs;
840                         chomp @ifs;
841                         foreach my $line(@ifs) {
842                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
843                                 my $destination;
844                                 my $mask;
845                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
846                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
847                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
848                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
849                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
850                                         # destination matches route, save mac and exit
851                                         $result= &get_ip($Iface);
852                                         last;
853                                 }
854                         }
855                 }
856         } else {
857                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
858         }
859         return $result;
863 sub send_msg_to_target {
864     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
865     my $error = 0;
866     my $header;
867     my $new_status;
868     my $act_status;
869     my ($sql_statement, $res);
870   
871     if( $msg_header ) {
872         $header = "'$msg_header'-";
873     } else {
874         $header = "";
875     }
877         # Patch the source ip
878         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
879                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
880                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
881         }
883     # encrypt xml msg
884     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
886     # opensocket
887     my $socket = &open_socket($address);
888     if( !$socket ) {
889         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
890         $error++;
891     }
892     
893     if( $error == 0 ) {
894         # send xml msg
895         print $socket $crypted_msg."\n";
897         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
898         #daemon_log("DEBUG: message:\n$msg", 9);
899         
900     }
902     # close socket in any case
903     if( $socket ) {
904         close $socket;
905     }
907     if( $error > 0 ) { $new_status = "down"; }
908     else { $new_status = $msg_header; }
911     # known_clients
912     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
913     $res = $known_clients_db->select_dbentry($sql_statement);
914     if( keys(%$res) > 0) {
915         $act_status = $res->{1}->{'status'};
916         if( $act_status eq "down" ) {
917             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
918             $res = $known_clients_db->del_dbentry($sql_statement);
919             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
920         } else { 
921             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
922             $res = $known_clients_db->update_dbentry($sql_statement);
923             if($new_status eq "down"){
924                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
925             } else {
926                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
927             }
928         }
929     }
931     # known_server
932     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
933     $res = $known_server_db->select_dbentry($sql_statement);
934     if( keys(%$res) > 0 ) {
935         $act_status = $res->{1}->{'status'};
936         if( $act_status eq "down" ) {
937             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
938             $res = $known_server_db->del_dbentry($sql_statement);
939             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
940         } 
941         else { 
942             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
943             $res = $known_server_db->update_dbentry($sql_statement);
944             if($new_status eq "down"){
945                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
946             }
947             else {
948                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
949             }
950         }
951     }
952     return $error; 
956 sub update_jobdb_status_for_send_msgs {
957     my ($answer, $error) = @_;
958     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
959         my $jobdb_id = $1;
960             
961         # sending msg faild
962         if( $error ) {
963             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
964                 my $sql_statement = "UPDATE $job_queue_tn ".
965                     "SET status='error', result='can not deliver msg, please consult log file' ".
966                     "WHERE id=$jobdb_id";
967                 my $res = $job_db->update_dbentry($sql_statement);
968             }
970         # sending msg was successful
971         } else {
972             my $sql_statement = "UPDATE $job_queue_tn ".
973                 "SET status='done' ".
974                 "WHERE id=$jobdb_id AND status='processed'";
975             my $res = $job_db->update_dbentry($sql_statement);
976         }
977     }
980 sub _start {
981     my ($kernel) = $_[KERNEL];
982     &trigger_db_loop($kernel);
983     $global_kernel = $kernel;
984         $kernel->yield('create_fai_server_db', $fai_server_tn );
985         $kernel->yield('create_fai_release_db', $fai_release_tn );
986         $kernel->sig(USR1 => "sig_handler");
987         $kernel->sig(USR2 => "create_packages_list_db");
990 sub sig_handler {
991         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
992         daemon_log("0 INFO got signal '$signal'", 1); 
993         $kernel->sig_handled();
994         return;
997 sub next_task {
998     my ($session, $heap) = @_[SESSION, HEAP];
1000     while ( keys( %{ $heap->{task} } ) < $max_children ) {
1001         my $next_task = shift @tasks;
1002         last unless defined $next_task;
1004         my $task = POE::Wheel::Run->new(
1005                 Program => sub { process_task($session, $heap, $next_task) },
1006                 StdioFilter => POE::Filter::Reference->new(),
1007                 StdoutEvent  => "task_result",
1008                 StderrEvent  => "task_debug",
1009                 CloseEvent   => "task_done",
1010                );
1012         $heap->{task}->{ $task->ID } = $task;
1013     }
1016 sub handle_task_result {
1017     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1018     my $client_answer = $result->{'answer'};
1019     if( $client_answer =~ s/session_id=(\d+)$// ) {
1020         my $session_id = $1;
1021         if( defined $session_id ) {
1022             my $session_reference = $kernel->ID_id_to_session($session_id);
1023             if( defined $session_reference ) {
1024                 $heap = $session_reference->get_heap();
1025             }
1026         }
1028         if(exists $heap->{'client'}) {
1029             $heap->{'client'}->put($client_answer);
1030         }
1031     }
1032     $kernel->sig(CHLD => "child_reap");
1035 sub handle_task_debug {
1036     my $result = $_[ARG0];
1037     print STDERR "$result\n";
1040 sub handle_task_done {
1041     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1042     delete $heap->{task}->{$task_id};
1043     $kernel->yield("next_task");
1046 sub process_task {
1047     no strict "refs";
1048     my ($session, $heap, $input) = @_;
1049     my $session_id = $session->ID;
1050     my ($msg, $msg_hash, $module);
1051     my $error = 0;
1052     my $answer_l;
1053     my ($answer_header, @answer_target_l, $answer_source);
1054     my $client_answer = "";
1056     daemon_log("", 5); 
1057     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1058     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1060     ####################
1061     # check incoming msg
1062     # msg is from a new client or gosa
1063     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1064     # msg is from a gosa-si-server or gosa-si-bus
1065     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1066         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1067     }
1068     # msg is from a gosa-si-client
1069     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1070         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1071     }
1072     # an error occurred
1073     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1074         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1075         # could not understand a msg from its server the client cause a re-registering process
1076         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);
1077         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1078         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1079         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1080             my $host_name = $hit->{'hostname'};
1081             my $host_key = $hit->{'hostkey'};
1082             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1083             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1084             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1085         }
1086         $error++;
1087     }
1089     ######################
1090     # process incoming msg
1091     if( $error == 0) {
1092         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1093                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1094         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1095         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1097         if ( 0 < @{$answer_l} ) {
1098             my $answer_str = join("\n", @{$answer_l});
1099             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1100         } else {
1101             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1102         }
1104     }
1105     if( !$answer_l ) { $error++ };
1107     ########
1108     # answer
1109     if( $error == 0 ) {
1111         foreach my $answer ( @{$answer_l} ) {
1112             # for each answer in answer list
1113             
1114             # check outgoing msg to xml validity
1115             my $answer_hash = &check_outgoing_xml_validity($answer);
1116             if( not defined $answer_hash ) {
1117                 next;
1118             }
1119             
1120             $answer_header = @{$answer_hash->{'header'}}[0];
1121             @answer_target_l = @{$answer_hash->{'target'}};
1122             $answer_source = @{$answer_hash->{'source'}}[0];
1124             # deliver msg to all targets 
1125             foreach my $answer_target ( @answer_target_l ) {
1127                 # targets of msg are all gosa-si-clients in known_clients_db
1128                 if( $answer_target eq "*" ) {
1129                     # answer is for all clients
1130                     my $sql_statement= "SELECT * FROM known_clients";
1131                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1132                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1133                         my $host_name = $hit->{hostname};
1134                         my $host_key = $hit->{hostkey};
1135                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1136                         &update_jobdb_status_for_send_msgs($answer, $error);
1137                     }
1138                 }
1140                 # targets of msg are all gosa-si-server in known_server_db
1141                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1142                     # answer is for all server in known_server
1143                     my $sql_statement= "SELECT * FROM known_server";
1144                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1145                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1146                         my $host_name = $hit->{hostname};
1147                         my $host_key = $hit->{hostkey};
1148                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1149                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1150                         &update_jobdb_status_for_send_msgs($answer, $error);
1151                     }
1152                 }
1154                 # target of msg is GOsa
1155                                 elsif( $answer_target eq "GOSA" ) {
1156                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1157                                         my $add_on = "";
1158                     if( defined $session_id ) {
1159                         $add_on = ".session_id=$session_id";
1160                     }
1161                     # answer is for GOSA and has to returned to connected client
1162                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1163                     $client_answer = $gosa_answer.$add_on;
1164                 }
1166                 # target of msg is job queue at this host
1167                 elsif( $answer_target eq "JOBDB") {
1168                     $answer =~ /<header>(\S+)<\/header>/;   
1169                     my $header;
1170                     if( defined $1 ) { $header = $1; }
1171                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1172                     &update_jobdb_status_for_send_msgs($answer, $error);
1173                 }
1175                 # target of msg is a mac address
1176                 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 ) {
1177                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1178                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1179                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1180                     my $found_ip_flag = 0;
1181                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1182                         my $host_name = $hit->{hostname};
1183                         my $host_key = $hit->{hostkey};
1184                         $answer =~ s/$answer_target/$host_name/g;
1185                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1186                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1187                         &update_jobdb_status_for_send_msgs($answer, $error);
1188                         $found_ip_flag++ ;
1189                     }   
1190                     if( $found_ip_flag == 0) {
1191                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1192                         if( $bus_activ eq "true" ) { 
1193                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1194                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1195                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1196                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1197                                 my $bus_address = $hit->{hostname};
1198                                 my $bus_key = $hit->{hostkey};
1199                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1200                                 &update_jobdb_status_for_send_msgs($answer, $error);
1201                                 last;
1202                             }
1203                         }
1205                     }
1207                 #  answer is for one specific host   
1208                 } else {
1209                     # get encrypt_key
1210                     my $encrypt_key = &get_encrypt_key($answer_target);
1211                     if( not defined $encrypt_key ) {
1212                         # unknown target, forward msg to bus
1213                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1214                         if( $bus_activ eq "true" ) { 
1215                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1216                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1217                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1218                             my $res_length = keys( %{$query_res} );
1219                             if( $res_length == 0 ){
1220                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1221                                         "no bus found in known_server", 3);
1222                             }
1223                             else {
1224                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1225                                     my $bus_key = $hit->{hostkey};
1226                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1227                                     &update_jobdb_status_for_send_msgs($answer, $error);
1228                                 }
1229                             }
1230                         }
1231                         next;
1232                     }
1233                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1234                     &update_jobdb_status_for_send_msgs($answer, $error);
1235                 }
1236             }
1237         }
1238     }
1240     my $filter = POE::Filter::Reference->new();
1241     my %result = ( 
1242             status => "seems ok to me",
1243             answer => $client_answer,
1244             );
1246     my $output = $filter->put( [ \%result ] );
1247     print @$output;
1253 sub trigger_db_loop {
1254         my ($kernel) = @_ ;
1255         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1256         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1257         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1258     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1259         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1263 sub watch_for_done_jobs {
1264     my ($kernel,$heap) = @_[KERNEL, HEAP];
1266     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1267         " WHERE status='done'";
1268         my $res = $job_db->select_dbentry( $sql_statement );
1270     while( my ($id, $hit) = each %{$res} ) {
1271         my $jobdb_id = $hit->{id};
1272         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1273         my $res = $job_db->del_dbentry($sql_statement); 
1274     }
1276     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1280 sub watch_for_new_jobs {
1281         if($watch_for_new_jobs_in_progress == 0) {
1282                 $watch_for_new_jobs_in_progress = 1;
1283                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1285                 # check gosa job queue for jobs with executable timestamp
1286                 my $timestamp = &get_time();
1287                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1288                 my $res = $job_db->exec_statement( $sql_statement );
1290                 # Merge all new jobs that would do the same actions
1291                 my @drops;
1292                 my $hits;
1293                 foreach my $hit (reverse @{$res} ) {
1294                         my $macaddress= lc @{$hit}[8];
1295                         my $headertag= @{$hit}[5];
1296                         if(
1297                                 defined($hits->{$macaddress}) &&
1298                                 defined($hits->{$macaddress}->{$headertag}) &&
1299                                 defined($hits->{$macaddress}->{$headertag}[0])
1300                         ) {
1301                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1302                         }
1303                         $hits->{$macaddress}->{$headertag}= $hit;
1304                 }
1306                 # Delete new jobs with a matching job in state 'processing'
1307                 foreach my $macaddress (keys %{$hits}) {
1308                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1309                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1310                                 if(defined($jobdb_id)) {
1311                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1312                                         my $res = $job_db->exec_statement( $sql_statement );
1313                                         foreach my $hit (@{$res}) {
1314                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1315                                         }
1316                                 } else {
1317                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1318                                 }
1319                         }
1320                 }
1322                 # Commit deletion
1323                 $job_db->exec_statementlist(\@drops);
1325                 # Look for new jobs that could be executed
1326                 foreach my $macaddress (keys %{$hits}) {
1328                         # Look if there is an executing job
1329                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1330                         my $res = $job_db->exec_statement( $sql_statement );
1332                         # Skip new jobs for host if there is a processing job
1333                         if(defined($res) and defined @{$res}[0]) {
1334                                 next;
1335                         }
1337                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1338                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1339                                 if(defined($jobdb_id)) {
1340                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1342                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1343                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1344                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1346                                         # expect macaddress is unique!!!!!!
1347                                         my $target = $res_hash->{1}->{hostname};
1349                                         # change header
1350                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1352                                         # add sqlite_id
1353                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1355                                         $job_msg =~ /<header>(\S+)<\/header>/;
1356                                         my $header = $1 ;
1357                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1359                                         # update status in job queue to 'processing'
1360                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1361                                         my $res = $job_db->update_dbentry($sql_statement);
1362 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1364                                         # We don't want parallel processing
1365                                         last;
1366                                 }
1367                         }
1368                 }
1370                 $watch_for_new_jobs_in_progress = 0;
1371                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1372         }
1376 sub watch_for_new_messages {
1377     my ($kernel,$heap) = @_[KERNEL, HEAP];
1378     my @coll_user_msg;   # collection list of outgoing messages
1379     
1380     # check messaging_db for new incoming messages with executable timestamp
1381     my $timestamp = &get_time();
1382     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1383     my $res = $messaging_db->exec_statement( $sql_statement );
1384         foreach my $hit (@{$res}) {
1386         # create outgoing messages
1387         my $message_to = @{$hit}[3];
1388         # translate message_to to plain login name
1389         my @message_to_l = split(/,/, $message_to);  
1390                 my %receiver_h; 
1391                 foreach my $receiver (@message_to_l) {
1392                         if ($receiver =~ /^u_([\s\S]*)$/) {
1393                                 $receiver_h{$1} = 0;
1394                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1395 # TODO implement receiver translation
1396                         } else {
1397                                 my $sbjct = &encode_base64(@{$hit}[1]);
1398                                 my $msg = &encode_base64(@{$hit}[7]);
1399                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message 'sbjct - msg'", 3); 
1400                         }
1401                 }
1402                 my @receiver_l = keys(%receiver_h);
1404         my $message_id = @{$hit}[0];
1406         #add each outgoing msg to messaging_db
1407         my $receiver;
1408         foreach $receiver (@receiver_l) {
1409             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1410                 "VALUES ('".
1411                 $message_id."', '".    # id
1412                 @{$hit}[1]."', '".     # subject
1413                 @{$hit}[2]."', '".     # message_from
1414                 $receiver."', '".      # message_to
1415                 "none"."', '".         # flag
1416                 "out"."', '".          # direction
1417                 @{$hit}[6]."', '".     # delivery_time
1418                 @{$hit}[7]."', '".     # message
1419                 $timestamp."'".     # timestamp
1420                 ")";
1421             &daemon_log("M DEBUG: $sql_statement", 1);
1422             my $res = $messaging_db->exec_statement($sql_statement);
1423             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1424         }
1426         # set incoming message to flag d=deliverd
1427         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1428         &daemon_log("M DEBUG: $sql_statement", 7);
1429         $res = $messaging_db->update_dbentry($sql_statement);
1430         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1431     }
1433     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1434     return;
1437 sub watch_for_delivery_messages {
1438     my ($kernel, $heap) = @_[KERNEL, HEAP];
1440     # select outgoing messages
1441     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1442     #&daemon_log("0 DEBUG: $sql", 7);
1443     my $res = $messaging_db->exec_statement( $sql_statement );
1444     
1445     # build out msg for each    usr
1446     foreach my $hit (@{$res}) {
1447         my $receiver = @{$hit}[3];
1448         my $msg_id = @{$hit}[0];
1449         my $subject = @{$hit}[1];
1450         my $message = @{$hit}[7];
1452         # resolve usr -> host where usr is logged in
1453         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1454         #&daemon_log("0 DEBUG: $sql", 7);
1455         my $res = $login_users_db->exec_statement($sql);
1457         # reciver is logged in nowhere
1458         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1460                 my $send_succeed = 0;
1461                 foreach my $hit (@$res) {
1462                                 my $receiver_host = @$hit[0];
1463                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1465                                 # fetch key to encrypt msg propperly for usr/host
1466                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1467                                 &daemon_log("0 DEBUG: $sql", 7);
1468                                 my $res = $known_clients_db->exec_statement($sql);
1470                                 # host is already down
1471                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1473                                 # host is on
1474                                 my $receiver_key = @{@{$res}[0]}[2];
1475                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1476                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1477                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1478                                 if ($error == 0 ) {
1479                                         $send_succeed++ ;
1480                                 }
1481                 }
1483                 if ($send_succeed) {
1484                                 # set outgoing msg at db to deliverd
1485                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1486                                 &daemon_log("0 DEBUG: $sql", 7);
1487                                 my $res = $messaging_db->exec_statement($sql); 
1488                 }
1489         }
1491     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1492     return;
1496 sub watch_for_done_messages {
1497     my ($kernel,$heap) = @_[KERNEL, HEAP];
1499     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1500     #&daemon_log("0 DEBUG: $sql", 7);
1501     my $res = $messaging_db->exec_statement($sql); 
1503     foreach my $hit (@{$res}) {
1504         my $msg_id = @{$hit}[0];
1506         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1507         #&daemon_log("0 DEBUG: $sql", 7); 
1508         my $res = $messaging_db->exec_statement($sql);
1510         # not all usr msgs have been seen till now
1511         if ( ref(@$res[0]) eq "ARRAY") { next; }
1512         
1513         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1514         #&daemon_log("0 DEBUG: $sql", 7);
1515         $res = $messaging_db->exec_statement($sql);
1516     
1517     }
1519     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1520     return;
1524 sub get_ldap_handle {
1525         my ($session_id) = @_;
1526         my $heap;
1527         my $ldap_handle;
1529         if (not defined $session_id ) { $session_id = 0 };
1530         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1532         if ($session_id == 0) {
1533                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1534                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1535                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1537         } else {
1538                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1539                 if( defined $session_reference ) {
1540                         $heap = $session_reference->get_heap();
1541                 }
1543                 if (not defined $heap) {
1544                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1545                         return;
1546                 }
1548                 # TODO: This "if" is nonsense, because it doesn't prove that the
1549                 #       used handle is still valid - or if we've to reconnect...
1550                 #if (not exists $heap->{ldap_handle}) {
1551                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1552                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1553                         $heap->{ldap_handle} = $ldap_handle;
1554                 #}
1555         }
1556         return $ldap_handle;
1560 sub change_fai_state {
1561     my ($st, $targets, $session_id) = @_;
1562     $session_id = 0 if not defined $session_id;
1563     # Set FAI state to localboot
1564     my %mapActions= (
1565         reboot    => '',
1566         update    => 'softupdate',
1567         localboot => 'localboot',
1568         reinstall => 'install',
1569         rescan    => '',
1570         wake      => '',
1571         memcheck  => 'memcheck',
1572         sysinfo   => 'sysinfo',
1573         install   => 'install',
1574     );
1576     # Return if this is unknown
1577     if (!exists $mapActions{ $st }){
1578         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1579       return;
1580     }
1582     my $state= $mapActions{ $st };
1584     my $ldap_handle = &get_ldap_handle($session_id);
1585     if( defined($ldap_handle) ) {
1587       # Build search filter for hosts
1588         my $search= "(&(objectClass=GOhard)";
1589         foreach (@{$targets}){
1590             $search.= "(macAddress=$_)";
1591         }
1592         $search.= ")";
1594       # If there's any host inside of the search string, procress them
1595         if (!($search =~ /macAddress/)){
1596             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1597             return;
1598         }
1600       # Perform search for Unit Tag
1601       my $mesg = $ldap_handle->search(
1602           base   => $ldap_base,
1603           scope  => 'sub',
1604           attrs  => ['dn', 'FAIstate', 'objectClass'],
1605           filter => "$search"
1606           );
1608           if ($mesg->count) {
1609                   my @entries = $mesg->entries;
1610                   foreach my $entry (@entries) {
1611                           # Only modify entry if it is not set to '$state'
1612                           if ($entry->get_value("FAIstate") ne "$state"){
1613                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1614                                   my $result;
1615                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1616                                   if (exists $tmp{'FAIobject'}){
1617                                           if ($state eq ''){
1618                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1619                                                           delete => [ FAIstate => [] ] ]);
1620                                           } else {
1621                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1622                                                           replace => [ FAIstate => $state ] ]);
1623                                           }
1624                                   } elsif ($state ne ''){
1625                                           $result= $ldap_handle->modify($entry->dn, changes => [
1626                                                   add     => [ objectClass => 'FAIobject' ],
1627                                                   add     => [ FAIstate => $state ] ]);
1628                                   }
1630                                   # Errors?
1631                                   if ($result->code){
1632                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1633                                   }
1634                           } else {
1635                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1636                           }  
1637                   }
1638           }
1639     # if no ldap handle defined
1640     } else {
1641         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1642     }
1647 sub change_goto_state {
1648     my ($st, $targets, $session_id) = @_;
1649     $session_id = 0  if not defined $session_id;
1651     # Switch on or off?
1652     my $state= $st eq 'active' ? 'active': 'locked';
1654     my $ldap_handle = &get_ldap_handle($session_id);
1655     if( defined($ldap_handle) ) {
1657       # Build search filter for hosts
1658       my $search= "(&(objectClass=GOhard)";
1659       foreach (@{$targets}){
1660         $search.= "(macAddress=$_)";
1661       }
1662       $search.= ")";
1664       # If there's any host inside of the search string, procress them
1665       if (!($search =~ /macAddress/)){
1666         return;
1667       }
1669       # Perform search for Unit Tag
1670       my $mesg = $ldap_handle->search(
1671           base   => $ldap_base,
1672           scope  => 'sub',
1673           attrs  => ['dn', 'gotoMode'],
1674           filter => "$search"
1675           );
1677       if ($mesg->count) {
1678         my @entries = $mesg->entries;
1679         foreach my $entry (@entries) {
1681           # Only modify entry if it is not set to '$state'
1682           if ($entry->get_value("gotoMode") ne $state){
1684             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1685             my $result;
1686             $result= $ldap_handle->modify($entry->dn, changes => [
1687                                                 replace => [ gotoMode => $state ] ]);
1689             # Errors?
1690             if ($result->code){
1691               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1692             }
1694           }
1695         }
1696       }
1698     }
1702 sub run_create_fai_server_db {
1703     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1704     my $session_id = $session->ID;
1705     my $task = POE::Wheel::Run->new(
1706             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1707             StdoutEvent  => "session_run_result",
1708             StderrEvent  => "session_run_debug",
1709             CloseEvent   => "session_run_done",
1710             );
1712     $heap->{task}->{ $task->ID } = $task;
1713     return;
1717 sub create_fai_server_db {
1718     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1719         my $result;
1721         if (not defined $session_id) { $session_id = 0; }
1722     my $ldap_handle = &get_ldap_handle();
1723         if(defined($ldap_handle)) {
1724                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1725                 my $mesg= $ldap_handle->search(
1726                         base   => $ldap_base,
1727                         scope  => 'sub',
1728                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1729                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1730                 );
1731                 if($mesg->{'resultCode'} == 0 &&
1732                    $mesg->count != 0) {
1733                    foreach my $entry (@{$mesg->{entries}}) {
1734                            if($entry->exists('FAIrepository')) {
1735                                    # Add an entry for each Repository configured for server
1736                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1737                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1738                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1739                                                    $result= $fai_server_db->add_dbentry( { 
1740                                                                    table => $table_name,
1741                                                                    primkey => ['server', 'release', 'tag'],
1742                                                                    server => $tmp_url,
1743                                                                    release => $tmp_release,
1744                                                                    sections => $tmp_sections,
1745                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1746                                                            } );
1747                                            }
1748                                    }
1749                            }
1750                    }
1751                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1753                 # TODO: Find a way to post the 'create_packages_list_db' event
1754                 if(not defined($dont_create_packages_list)) {
1755                         &create_packages_list_db(undef, undef, $session_id);
1756                 }
1757         }       
1758     
1759     $ldap_handle->disconnect;
1760         return $result;
1764 sub run_create_fai_release_db {
1765     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1766         my $session_id = $session->ID;
1767     my $task = POE::Wheel::Run->new(
1768             Program => sub { &create_fai_release_db($table_name, $session_id) },
1769             StdoutEvent  => "session_run_result",
1770             StderrEvent  => "session_run_debug",
1771             CloseEvent   => "session_run_done",
1772             );
1774     $heap->{task}->{ $task->ID } = $task;
1775     return;
1779 sub create_fai_release_db {
1780         my ($table_name, $session_id) = @_;
1781         my $result;
1783     # used for logging
1784     if (not defined $session_id) { $session_id = 0; }
1786     my $ldap_handle = &get_ldap_handle();
1787         if(defined($ldap_handle)) {
1788                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1789                 my $mesg= $ldap_handle->search(
1790                         base   => $ldap_base,
1791                         scope  => 'sub',
1792                         attrs  => [],
1793                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1794                 );
1795                 if($mesg->{'resultCode'} == 0 &&
1796                         $mesg->count != 0) {
1797                         # Walk through all possible FAI container ou's
1798                         my @sql_list;
1799                         my $timestamp= &get_time();
1800                         foreach my $ou (@{$mesg->{entries}}) {
1801                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1802                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1803                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1804                                         if(@tmp_array) {
1805                                                 foreach my $entry (@tmp_array) {
1806                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1807                                                                 my $sql= 
1808                                                                 "INSERT INTO $table_name "
1809                                                                 ."(timestamp, release, class, type, state) VALUES ("
1810                                                                 .$timestamp.","
1811                                                                 ."'".$entry->{'release'}."',"
1812                                                                 ."'".$entry->{'class'}."',"
1813                                                                 ."'".$entry->{'type'}."',"
1814                                                                 ."'".$entry->{'state'}."')";
1815                                                                 push @sql_list, $sql;
1816                                                         }
1817                                                 }
1818                                         }
1819                                 }
1820                         }
1822                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1823                         if(@sql_list) {
1824                                 unshift @sql_list, "VACUUM";
1825                                 unshift @sql_list, "DELETE FROM $table_name";
1826                                 $fai_release_db->exec_statementlist(\@sql_list);
1827                         }
1828                         daemon_log("$session_id DEBUG: Done with inserting",7);
1829                 }
1830                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1831         }
1832     $ldap_handle->disconnect;
1833         return $result;
1836 sub get_fai_types {
1837         my $tmp_classes = shift || return undef;
1838         my @result;
1840         foreach my $type(keys %{$tmp_classes}) {
1841                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1842                         my $entry = {
1843                                 type => $type,
1844                                 state => $tmp_classes->{$type}[0],
1845                         };
1846                         push @result, $entry;
1847                 }
1848         }
1850         return @result;
1853 sub get_fai_state {
1854         my $result = "";
1855         my $tmp_classes = shift || return $result;
1857         foreach my $type(keys %{$tmp_classes}) {
1858                 if(defined($tmp_classes->{$type}[0])) {
1859                         $result = $tmp_classes->{$type}[0];
1860                         
1861                 # State is equal for all types in class
1862                         last;
1863                 }
1864         }
1866         return $result;
1869 sub resolve_fai_classes {
1870         my ($fai_base, $ldap_handle, $session_id) = @_;
1871         if (not defined $session_id) { $session_id = 0; }
1872         my $result;
1873         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1874         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1875         my $fai_classes;
1877         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1878         my $mesg= $ldap_handle->search(
1879                 base   => $fai_base,
1880                 scope  => 'sub',
1881                 attrs  => ['cn','objectClass','FAIstate'],
1882                 filter => $fai_filter,
1883         );
1884         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1886         if($mesg->{'resultCode'} == 0 &&
1887                 $mesg->count != 0) {
1888                 foreach my $entry (@{$mesg->{entries}}) {
1889                         if($entry->exists('cn')) {
1890                                 my $tmp_dn= $entry->dn();
1892                                 # Skip classname and ou dn parts for class
1893                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1895                                 # Skip classes without releases
1896                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1897                                         next;
1898                                 }
1900                                 my $tmp_cn= $entry->get_value('cn');
1901                                 my $tmp_state= $entry->get_value('FAIstate');
1903                                 my $tmp_type;
1904                                 # Get FAI type
1905                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1906                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1907                                                 $tmp_type= $oclass;
1908                                                 last;
1909                                         }
1910                                 }
1912                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1913                                         # A Subrelease
1914                                         my @sub_releases = split(/,/, $tmp_release);
1916                                         # Walk through subreleases and build hash tree
1917                                         my $hash;
1918                                         while(my $tmp_sub_release = pop @sub_releases) {
1919                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1920                                         }
1921                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1922                                 } else {
1923                                         # A branch, no subrelease
1924                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1925                                 }
1926                         } elsif (!$entry->exists('cn')) {
1927                                 my $tmp_dn= $entry->dn();
1928                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1930                                 # Skip classes without releases
1931                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1932                                         next;
1933                                 }
1935                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1936                                         # A Subrelease
1937                                         my @sub_releases= split(/,/, $tmp_release);
1939                                         # Walk through subreleases and build hash tree
1940                                         my $hash;
1941                                         while(my $tmp_sub_release = pop @sub_releases) {
1942                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1943                                         }
1944                                         # Remove the last two characters
1945                                         chop($hash);
1946                                         chop($hash);
1948                                         eval('$fai_classes->'.$hash.'= {}');
1949                                 } else {
1950                                         # A branch, no subrelease
1951                                         if(!exists($fai_classes->{$tmp_release})) {
1952                                                 $fai_classes->{$tmp_release} = {};
1953                                         }
1954                                 }
1955                         }
1956                 }
1958                 # The hash is complete, now we can honor the copy-on-write based missing entries
1959                 foreach my $release (keys %$fai_classes) {
1960                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1961                 }
1962         }
1963         return $result;
1966 sub apply_fai_inheritance {
1967        my $fai_classes = shift || return {};
1968        my $tmp_classes;
1970        # Get the classes from the branch
1971        foreach my $class (keys %{$fai_classes}) {
1972                # Skip subreleases
1973                if($class =~ /^ou=.*$/) {
1974                        next;
1975                } else {
1976                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1977                }
1978        }
1980        # Apply to each subrelease
1981        foreach my $subrelease (keys %{$fai_classes}) {
1982                if($subrelease =~ /ou=/) {
1983                        foreach my $tmp_class (keys %{$tmp_classes}) {
1984                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1985                                        $fai_classes->{$subrelease}->{$tmp_class} =
1986                                        deep_copy($tmp_classes->{$tmp_class});
1987                                } else {
1988                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1989                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1990                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1991                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1992                                                }
1993                                        }
1994                                }
1995                        }
1996                }
1997        }
1999        # Find subreleases in deeper levels
2000        foreach my $subrelease (keys %{$fai_classes}) {
2001                if($subrelease =~ /ou=/) {
2002                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2003                                if($subsubrelease =~ /ou=/) {
2004                                        apply_fai_inheritance($fai_classes->{$subrelease});
2005                                }
2006                        }
2007                }
2008        }
2010        return $fai_classes;
2013 sub get_fai_release_entries {
2014         my $tmp_classes = shift || return;
2015         my $parent = shift || "";
2016         my @result = shift || ();
2018         foreach my $entry (keys %{$tmp_classes}) {
2019                 if(defined($entry)) {
2020                         if($entry =~ /^ou=.*$/) {
2021                                 my $release_name = $entry;
2022                                 $release_name =~ s/ou=//g;
2023                                 if(length($parent)>0) {
2024                                         $release_name = $parent."/".$release_name;
2025                                 }
2026                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2027                                 foreach my $bufentry(@bufentries) {
2028                                         push @result, $bufentry;
2029                                 }
2030                         } else {
2031                                 my @types = get_fai_types($tmp_classes->{$entry});
2032                                 foreach my $type (@types) {
2033                                         push @result, 
2034                                         {
2035                                                 'class' => $entry,
2036                                                 'type' => $type->{'type'},
2037                                                 'release' => $parent,
2038                                                 'state' => $type->{'state'},
2039                                         };
2040                                 }
2041                         }
2042                 }
2043         }
2045         return @result;
2048 sub deep_copy {
2049         my $this = shift;
2050         if (not ref $this) {
2051                 $this;
2052         } elsif (ref $this eq "ARRAY") {
2053                 [map deep_copy($_), @$this];
2054         } elsif (ref $this eq "HASH") {
2055                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2056         } else { die "what type is $_?" }
2060 sub session_run_result {
2061     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2062     $kernel->sig(CHLD => "child_reap");
2065 sub session_run_debug {
2066     my $result = $_[ARG0];
2067     print STDERR "$result\n";
2070 sub session_run_done {
2071     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2072     delete $heap->{task}->{$task_id};
2076 sub create_sources_list {
2077         my $session_id = shift;
2078         my $ldap_handle = &main::get_ldap_handle;
2079         my $result="/tmp/gosa_si_tmp_sources_list";
2081         # Remove old file
2082         if(stat($result)) {
2083                 unlink($result);
2084                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2085         }
2087         my $fh;
2088         open($fh, ">$result");
2089         if (not defined $fh) {
2090                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2091                 return undef;
2092         }
2093         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2094                 my $mesg=$ldap_handle->search(
2095                         base    => $main::ldap_server_dn,
2096                         scope   => 'base',
2097                         attrs   => 'FAIrepository',
2098                         filter  => 'objectClass=FAIrepositoryServer'
2099                 );
2100                 if($mesg->count) {
2101                         foreach my $entry(@{$mesg->{'entries'}}) {
2102                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2103                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2104                                         my $line = "deb $server $release";
2105                                         $sections =~ s/,/ /g;
2106                                         $line.= " $sections";
2107                                         print $fh $line."\n";
2108                                 }
2109                         }
2110                 }
2111         } else {
2112                 if (defined $main::ldap_server_dn){
2113                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2114                 } else {
2115                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2116                 }
2117         }
2118         close($fh);
2120         return $result;
2124 sub run_create_packages_list_db {
2125     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2126         my $session_id = $session->ID;
2128         my $task = POE::Wheel::Run->new(
2129                                         Priority => +20,
2130                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2131                                         StdoutEvent  => "session_run_result",
2132                                         StderrEvent  => "session_run_debug",
2133                                         CloseEvent   => "session_run_done",
2134                                         );
2135         $heap->{task}->{ $task->ID } = $task;
2139 sub create_packages_list_db {
2140         my ($ldap_handle, $sources_file, $session_id) = @_;
2141         
2142         # it should not be possible to trigger a recreation of packages_list_db
2143         # while packages_list_db is under construction, so set flag packages_list_under_construction
2144         # which is tested befor recreation can be started
2145         if (-r $packages_list_under_construction) {
2146                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2147                 return;
2148         } else {
2149                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2150                 # set packages_list_under_construction to true
2151                 system("touch $packages_list_under_construction");
2152                 @packages_list_statements=();
2153         }
2155         if (not defined $session_id) { $session_id = 0; }
2156         if (not defined $ldap_handle) { 
2157                 $ldap_handle= &get_ldap_handle();
2159                 if (not defined $ldap_handle) {
2160                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2161                         unlink($packages_list_under_construction);
2162                         return;
2163                 }
2164         }
2165         if (not defined $sources_file) { 
2166                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2167                 $sources_file = &create_sources_list($session_id);
2168         }
2170         if (not defined $sources_file) {
2171                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2172                 unlink($packages_list_under_construction);
2173                 return;
2174         }
2176         my $line;
2178         open(CONFIG, "<$sources_file") or do {
2179                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2180                 unlink($packages_list_under_construction);
2181                 return;
2182         };
2184         # Read lines
2185         while ($line = <CONFIG>){
2186                 # Unify
2187                 chop($line);
2188                 $line =~ s/^\s+//;
2189                 $line =~ s/^\s+/ /;
2191                 # Strip comments
2192                 $line =~ s/#.*$//g;
2194                 # Skip empty lines
2195                 if ($line =~ /^\s*$/){
2196                         next;
2197                 }
2199                 # Interpret deb line
2200                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2201                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2202                         my $section;
2203                         foreach $section (split(' ', $sections)){
2204                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2205                         }
2206                 }
2207         }
2209         close (CONFIG);
2211         find(\&cleanup_and_extract, keys( %repo_dirs ));
2212         &main::strip_packages_list_statements();
2213         unshift @packages_list_statements, "VACUUM";
2214         $packages_list_db->exec_statementlist(\@packages_list_statements);
2215         unlink($packages_list_under_construction);
2216         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2217         return;
2220 # This function should do some intensive task to minimize the db-traffic
2221 sub strip_packages_list_statements {
2222     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2223         my @new_statement_list=();
2224         my $hash;
2225         my $insert_hash;
2226         my $update_hash;
2227         my $delete_hash;
2228         my $local_timestamp=get_time();
2230         foreach my $existing_entry (@existing_entries) {
2231                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2232         }
2234         foreach my $statement (@packages_list_statements) {
2235                 if($statement =~ /^INSERT/i) {
2236                         # Assign the values from the insert statement
2237                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2238                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2239                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2240                                 # If section or description has changed, update the DB
2241                                 if( 
2242                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2243                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2244                                 ) {
2245                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2246                                 }
2247                         } else {
2248                                 # Insert a non-existing entry to db
2249                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2250                         }
2251                 } elsif ($statement =~ /^UPDATE/i) {
2252                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2253                         /^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;
2254                         foreach my $distribution (keys %{$hash}) {
2255                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2256                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2257                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2258                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2259                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2260                                                 my $section;
2261                                                 my $description;
2262                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2263                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2264                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2265                                                 }
2266                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2267                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2268                                                 }
2269                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2270                                         }
2271                                 }
2272                         }
2273                 }
2274         }
2276         # TODO: Check for orphaned entries
2278         # unroll the insert_hash
2279         foreach my $distribution (keys %{$insert_hash}) {
2280                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2281                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2282                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2283                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2284                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2285                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2286                                 ."'$local_timestamp')";
2287                         }
2288                 }
2289         }
2291         # unroll the update hash
2292         foreach my $distribution (keys %{$update_hash}) {
2293                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2294                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2295                                 my $set = "";
2296                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2297                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2298                                 }
2299                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2300                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2301                                 }
2302                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2303                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2304                                 }
2305                                 if(defined($set) and length($set) > 0) {
2306                                         $set .= "timestamp = '$local_timestamp'";
2307                                 } else {
2308                                         next;
2309                                 }
2310                                 push @new_statement_list, 
2311                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2312                                         ." distribution = '$distribution'"
2313                                         ." AND package = '$package'"
2314                                         ." AND version = '$version'";
2315                         }
2316                 }
2317         }
2319         @packages_list_statements = @new_statement_list;
2323 sub parse_package_info {
2324     my ($baseurl, $dist, $section, $session_id)= @_;
2325     my ($package);
2326     if (not defined $session_id) { $session_id = 0; }
2327     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2328     $repo_dirs{ "${repo_path}/pool" } = 1;
2330     foreach $package ("Packages.gz"){
2331         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2332         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2333         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2334     }
2335     
2339 sub get_package {
2340     my ($url, $dest, $session_id)= @_;
2341     if (not defined $session_id) { $session_id = 0; }
2343     my $tpath = dirname($dest);
2344     -d "$tpath" || mkpath "$tpath";
2346     # This is ugly, but I've no time to take a look at "how it works in perl"
2347     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2348         system("gunzip -cd '$dest' > '$dest.in'");
2349         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2350         unlink($dest);
2351         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2352     } else {
2353         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2354     }
2355     return 0;
2359 sub parse_package {
2360     my ($path, $dist, $srv_path, $session_id)= @_;
2361     if (not defined $session_id) { $session_id = 0;}
2362     my ($package, $version, $section, $description);
2363     my $PACKAGES;
2364     my $timestamp = &get_time();
2366     if(not stat("$path.in")) {
2367         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2368         return;
2369     }
2371     open($PACKAGES, "<$path.in");
2372     if(not defined($PACKAGES)) {
2373         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2374         return;
2375     }
2377     # Read lines
2378     while (<$PACKAGES>){
2379         my $line = $_;
2380         # Unify
2381         chop($line);
2383         # Use empty lines as a trigger
2384         if ($line =~ /^\s*$/){
2385             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2386             push(@packages_list_statements, $sql);
2387             $package = "none";
2388             $version = "none";
2389             $section = "none";
2390             $description = "none"; 
2391             next;
2392         }
2394         # Trigger for package name
2395         if ($line =~ /^Package:\s/){
2396             ($package)= ($line =~ /^Package: (.*)$/);
2397             next;
2398         }
2400         # Trigger for version
2401         if ($line =~ /^Version:\s/){
2402             ($version)= ($line =~ /^Version: (.*)$/);
2403             next;
2404         }
2406         # Trigger for description
2407         if ($line =~ /^Description:\s/){
2408             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2409             next;
2410         }
2412         # Trigger for section
2413         if ($line =~ /^Section:\s/){
2414             ($section)= ($line =~ /^Section: (.*)$/);
2415             next;
2416         }
2418         # Trigger for filename
2419         if ($line =~ /^Filename:\s/){
2420             my ($filename) = ($line =~ /^Filename: (.*)$/);
2421             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2422             next;
2423         }
2424     }
2426     close( $PACKAGES );
2427     unlink( "$path.in" );
2428     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2432 sub store_fileinfo {
2433     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2435     my %fileinfo = (
2436         'package' => $package,
2437         'dist' => $dist,
2438         'version' => $vers,
2439     );
2441     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2445 sub cleanup_and_extract {
2446     my $fileinfo = $repo_files{ $File::Find::name };
2448     if( defined $fileinfo ) {
2450         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2451         my $sql;
2452         my $package = $fileinfo->{ 'package' };
2453         my $newver = $fileinfo->{ 'version' };
2455         mkpath($dir);
2456         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2458                 if( -f "$dir/DEBIAN/templates" ) {
2460                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2462                         my $tmpl= "";
2463                         {
2464                                 local $/=undef;
2465                                 open FILE, "$dir/DEBIAN/templates";
2466                                 $tmpl = &encode_base64(<FILE>);
2467                                 close FILE;
2468                         }
2469                         rmtree("$dir/DEBIAN/templates");
2471                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2472                 push @packages_list_statements, $sql;
2473                 }
2474     }
2476     return;
2480 #==== MAIN = main ==============================================================
2481 #  parse commandline options
2482 Getopt::Long::Configure( "bundling" );
2483 GetOptions("h|help" => \&usage,
2484         "c|config=s" => \$cfg_file,
2485         "f|foreground" => \$foreground,
2486         "v|verbose+" => \$verbose,
2487         "no-bus+" => \$no_bus,
2488         "no-arp+" => \$no_arp,
2489            );
2491 #  read and set config parameters
2492 &check_cmdline_param ;
2493 &read_configfile;
2494 &check_pid;
2496 $SIG{CHLD} = 'IGNORE';
2498 # forward error messages to logfile
2499 if( ! $foreground ) {
2500   open( STDIN,  '+>/dev/null' );
2501   open( STDOUT, '+>&STDIN'    );
2502   open( STDERR, '+>&STDIN'    );
2505 # Just fork, if we are not in foreground mode
2506 if( ! $foreground ) { 
2507     chdir '/'                 or die "Can't chdir to /: $!";
2508     $pid = fork;
2509     setsid                    or die "Can't start a new session: $!";
2510     umask 0;
2511 } else { 
2512     $pid = $$; 
2515 # Do something useful - put our PID into the pid_file
2516 if( 0 != $pid ) {
2517     open( LOCK_FILE, ">$pid_file" );
2518     print LOCK_FILE "$pid\n";
2519     close( LOCK_FILE );
2520     if( !$foreground ) { 
2521         exit( 0 ) 
2522     };
2525 # parse head url and revision from svn
2526 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2527 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2528 $server_headURL = defined $1 ? $1 : 'unknown' ;
2529 $server_revision = defined $2 ? $2 : 'unknown' ;
2530 if ($server_headURL =~ /\/tag\// || 
2531         $server_headURL =~ /\/branches\// ) {
2532     $server_status = "stable"; 
2533 } else {
2534     $server_status = "developmental" ;
2538 daemon_log(" ", 1);
2539 daemon_log("$0 started!", 1);
2540 daemon_log("status: $server_status", 1);
2541 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2543 if ($no_bus > 0) {
2544     $bus_activ = "false"
2547 # connect to gosa-si job queue
2548 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2549 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2551 # connect to known_clients_db
2552 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2553 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2555 # connect to known_server_db
2556 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2557 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2559 # connect to login_usr_db
2560 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2561 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2563 # connect to fai_server_db and fai_release_db
2564 unlink($fai_server_file_name);
2565 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2566 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2568 unlink($fai_release_file_name);
2569 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2570 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2572 # connect to packages_list_db
2573 #unlink($packages_list_file_name);
2574 unlink($packages_list_under_construction);
2575 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2576 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2578 # connect to messaging_db
2579 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2580 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2583 # create xml object used for en/decrypting
2584 $xml = new XML::Simple();
2586 # create socket for incoming xml messages
2588 POE::Component::Server::TCP->new(
2589         Port => $server_port,
2590         ClientInput => sub {
2591         my ($kernel, $input) = @_[KERNEL, ARG0];
2592         push(@tasks, $input);
2593         $kernel->yield("next_task");
2594         },
2595     InlineStates => {
2596         next_task => \&next_task,
2597         task_result => \&handle_task_result,
2598         task_done   => \&handle_task_done,
2599         task_debug  => \&handle_task_debug,
2600         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2601     }
2602 );
2604 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2606 # create session for repeatedly checking the job queue for jobs
2607 POE::Session->create(
2608         inline_states => {
2609                 _start => \&_start,
2610                 sig_handler => \&sig_handler,
2611         watch_for_new_messages => \&watch_for_new_messages,
2612         watch_for_delivery_messages => \&watch_for_delivery_messages,
2613         watch_for_done_messages => \&watch_for_done_messages,
2614                 watch_for_new_jobs => \&watch_for_new_jobs,
2615         watch_for_done_jobs => \&watch_for_done_jobs,
2616         create_packages_list_db => \&run_create_packages_list_db,
2617         create_fai_server_db => \&run_create_fai_server_db,
2618         create_fai_release_db => \&run_create_fai_release_db,
2619         session_run_result => \&session_run_result,
2620         session_run_debug => \&session_run_debug,
2621         session_run_done => \&session_run_done,
2622         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2623         }
2624 );
2627 # import all modules
2628 &import_modules;
2630 # check wether all modules are gosa-si valid passwd check
2632 POE::Kernel->run();
2633 exit;