Code

Read and Write is required to restore a snapshot.
[gosa.git] / gosa-si / gosa-si-server-nobus
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file, 
86     $server_ip, $server_port, $ClientPackages_key, 
87     $arp_activ, $gosa_unit_tag,
88     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_bus = 0;
115 $bus_activ = "true";
116 $no_arp = 0;
117 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
118 my @packages_list_statements;
119 my $watch_for_new_jobs_in_progress = 0;
121 # holds all incoming decrypted messages
122 our $incoming_db;
123 our $incoming_tn = 'incoming';
124 my $incoming_file_name;
125 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
126         "timestamp DEFAULT 'none'", 
127         "headertag DEFAULT 'none'",
128                 "targettag DEFAULT 'none'",
129         "xmlmessage DEFAULT 'none'",
130         "module DEFAULT 'none'",
131         "sessionid DEFAULT '0'",
132         );
134 # holds all gosa jobs
135 our $job_db;
136 our $job_queue_tn = 'jobs';
137 my $job_queue_file_name;
138 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
139                 "timestamp DEFAULT 'none'", 
140                 "status DEFAULT 'none'", 
141                 "result DEFAULT 'none'", 
142                 "progress DEFAULT 'none'", 
143         "headertag DEFAULT 'none'", 
144                 "targettag DEFAULT 'none'", 
145                 "xmlmessage DEFAULT 'none'", 
146                 "macaddress DEFAULT 'none'",
147                 "plainname DEFAULT 'none'",
148                 );
150 # holds all other gosa-sd as well as the gosa-sd-bus
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients"; 
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client 
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server"; 
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release"; 
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386"; 
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging"; 
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
197         "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai"; 
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 %cfg_defaults = (
210 "general" => {
211     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
212     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
213     },
214 "bus" => {
215     "activ" => [\$bus_activ, "true"],
216     },
217 "server" => {
218     "port" => [\$server_port, "20081"],
219     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
229     "repo-path"            => [\$repo_path, '/srv/www/repository'],
230     "ldap-uri"             => [\$ldap_uri, ""],
231     "ldap-base"            => [\$ldap_base, ""],
232     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
233     "ldap-admin-password"  => [\$ldap_admin_password, ""],
234     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
235     "max-clients"          => [\$max_clients, 10],
236     },
237 "GOsaPackages" => {
238     "ip" => [\$gosa_ip, "0.0.0.0"],
239     "port" => [\$gosa_port, "20082"],
240     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
241     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
242     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
243     "key" => [\$GosaPackages_key, "none"],
244     },
245 "ClientPackages" => {
246     "key" => [\$ClientPackages_key, "none"],
247     },
248 "ServerPackages"=> {
249     "address"      => [\$foreign_server_string, ""],
250     "domain"  => [\$server_domain, ""],
251     "key"     => [\$ServerPackages_key, "none"],
252     "key-lifetime" => [\$foreign_servers_register_delay, 120],
254 );
257 #===  FUNCTION  ================================================================
258 #         NAME:  usage
259 #   PARAMETERS:  nothing
260 #      RETURNS:  nothing
261 #  DESCRIPTION:  print out usage text to STDERR
262 #===============================================================================
263 sub usage {
264     print STDERR << "EOF" ;
265 usage: $prg [-hvf] [-c config]
267            -h        : this (help) message
268            -c <file> : config file
269            -f        : foreground, process will not be forked to background
270            -v        : be verbose (multiple to increase verbosity)
271            -no-bus   : starts $prg without connection to bus
272            -no-arp   : starts $prg without connection to arp module
273  
274 EOF
275     print "\n" ;
279 #===  FUNCTION  ================================================================
280 #         NAME:  read_configfile
281 #   PARAMETERS:  cfg_file - string -
282 #      RETURNS:  nothing
283 #  DESCRIPTION:  read cfg_file and set variables
284 #===============================================================================
285 sub read_configfile {
286     my $cfg;
287     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
288         if( -r $cfg_file ) {
289             $cfg = Config::IniFiles->new( -file => $cfg_file );
290         } else {
291             print STDERR "Couldn't read config file!\n";
292         }
293     } else {
294         $cfg = Config::IniFiles->new() ;
295     }
296     foreach my $section (keys %cfg_defaults) {
297         foreach my $param (keys %{$cfg_defaults{ $section }}) {
298             my $pinfo = $cfg_defaults{ $section }{ $param };
299             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
300         }
301     }
305 #===  FUNCTION  ================================================================
306 #         NAME:  logging
307 #   PARAMETERS:  level - string - default 'info'
308 #                msg - string -
309 #                facility - string - default 'LOG_DAEMON'
310 #      RETURNS:  nothing
311 #  DESCRIPTION:  function for logging
312 #===============================================================================
313 sub daemon_log {
314     # log into log_file
315     my( $msg, $level ) = @_;
316     if(not defined $msg) { return }
317     if(not defined $level) { $level = 1 }
318     if(defined $log_file){
319         open(LOG_HANDLE, ">>$log_file");
320         chmod 0600, $log_file;
321         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
322             print STDERR "cannot open $log_file: $!";
323             return }
324             chomp($msg);
325                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
326             if($level <= $verbose){
327                 my ($seconds, $minutes, $hours, $monthday, $month,
328                         $year, $weekday, $yearday, $sommertime) = localtime(time);
329                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
330                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
331                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
332                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
333                 $month = $monthnames[$month];
334                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
335                 $year+=1900;
336                 my $name = $prg;
338                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
339                 print LOG_HANDLE $log_msg;
340                 if( $foreground ) { 
341                     print STDERR $log_msg;
342                 }
343             }
344         close( LOG_HANDLE );
345     }
349 #===  FUNCTION  ================================================================
350 #         NAME:  check_cmdline_param
351 #   PARAMETERS:  nothing
352 #      RETURNS:  nothing
353 #  DESCRIPTION:  validates commandline parameter
354 #===============================================================================
355 sub check_cmdline_param () {
356     my $err_config;
357     my $err_counter = 0;
358         if(not defined($cfg_file)) {
359                 $cfg_file = "/etc/gosa-si/server.conf";
360                 if(! -r $cfg_file) {
361                         $err_config = "please specify a config file";
362                         $err_counter += 1;
363                 }
364     }
365     if( $err_counter > 0 ) {
366         &usage( "", 1 );
367         if( defined( $err_config)) { print STDERR "$err_config\n"}
368         print STDERR "\n";
369         exit( -1 );
370     }
374 #===  FUNCTION  ================================================================
375 #         NAME:  check_pid
376 #   PARAMETERS:  nothing
377 #      RETURNS:  nothing
378 #  DESCRIPTION:  handels pid processing
379 #===============================================================================
380 sub check_pid {
381     $pid = -1;
382     # Check, if we are already running
383     if( open(LOCK_FILE, "<$pid_file") ) {
384         $pid = <LOCK_FILE>;
385         if( defined $pid ) {
386             chomp( $pid );
387             if( -f "/proc/$pid/stat" ) {
388                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
389                 if( $stat ) {
390                                         daemon_log("ERROR: Already running",1);
391                     close( LOCK_FILE );
392                     exit -1;
393                 }
394             }
395         }
396         close( LOCK_FILE );
397         unlink( $pid_file );
398     }
400     # create a syslog msg if it is not to possible to open PID file
401     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
402         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
403         if (open(LOCK_FILE, '<', $pid_file)
404                 && ($pid = <LOCK_FILE>))
405         {
406             chomp($pid);
407             $msg .= "(PID $pid)\n";
408         } else {
409             $msg .= "(unable to read PID)\n";
410         }
411         if( ! ($foreground) ) {
412             openlog( $0, "cons,pid", "daemon" );
413             syslog( "warning", $msg );
414             closelog();
415         }
416         else {
417             print( STDERR " $msg " );
418         }
419         exit( -1 );
420     }
423 #===  FUNCTION  ================================================================
424 #         NAME:  import_modules
425 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
426 #                are stored
427 #      RETURNS:  nothing
428 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
429 #                state is on is imported by "require 'file';"
430 #===============================================================================
431 sub import_modules {
432     daemon_log(" ", 1);
434     if (not -e $modules_path) {
435         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
436     }
438     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
439     while (defined (my $file = readdir (DIR))) {
440         if (not $file =~ /(\S*?).pm$/) {
441             next;
442         }
443                 my $mod_name = $1;
445         if( $file =~ /ArpHandler.pm/ ) {
446             if( $no_arp > 0 ) {
447                 next;
448             }
449         }
450         
451         eval { require $file; };
452         if ($@) {
453             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
454             daemon_log("$@", 5);
455                 } else {
456                         my $info = eval($mod_name.'::get_module_info()');
457                         # Only load module if get_module_info() returns a non-null object
458                         if( $info ) {
459                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
460                                 $known_modules->{$mod_name} = $info;
461                                 daemon_log("0 INFO: module $mod_name loaded", 5);
462                         }
463                 }
464     }   
465     close (DIR);
469 #===  FUNCTION  ================================================================
470 #         NAME:  sig_int_handler
471 #   PARAMETERS:  signal - string - signal arose from system
472 #      RETURNS:  noting
473 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
474 #===============================================================================
475 sub sig_int_handler {
476     my ($signal) = @_;
478 #       if (defined($ldap_handle)) {
479 #               $ldap_handle->disconnect;
480 #       }
481     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
482     
484     daemon_log("shutting down gosa-si-server", 1);
485     system("kill `ps -C gosa-si-server-nobus -o pid=`");
487 $SIG{INT} = \&sig_int_handler;
490 sub check_key_and_xml_validity {
491     my ($crypted_msg, $module_key, $session_id) = @_;
492     my $msg;
493     my $msg_hash;
494     my $error_string;
495     eval{
496         $msg = &decrypt_msg($crypted_msg, $module_key);
498         if ($msg =~ /<xml>/i){
499             $msg =~ s/\s+/ /g;  # just for better daemon_log
500             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
501             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
503             ##############
504             # check header
505             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
506             my $header_l = $msg_hash->{'header'};
507             if( 1 > @{$header_l} ) { die 'empty header tag'; }
508             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
509             my $header = @{$header_l}[0];
510             if( 0 == length $header) { die 'empty string in header tag'; }
512             ##############
513             # check source
514             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
515             my $source_l = $msg_hash->{'source'};
516             if( 1 > @{$source_l} ) { die 'empty source tag'; }
517             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
518             my $source = @{$source_l}[0];
519             if( 0 == length $source) { die 'source error'; }
521             ##############
522             # check target
523             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
524             my $target_l = $msg_hash->{'target'};
525             if( 1 > @{$target_l} ) { die 'empty target tag'; }
526         }
527     };
528     if($@) {
529         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
530         $msg = undef;
531         $msg_hash = undef;
532     }
534     return ($msg, $msg_hash);
538 sub check_outgoing_xml_validity {
539     my ($msg) = @_;
541     my $msg_hash;
542     eval{
543         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
545         ##############
546         # check header
547         my $header_l = $msg_hash->{'header'};
548         if( 1 != @{$header_l} ) {
549             die 'no or more than one headers specified';
550         }
551         my $header = @{$header_l}[0];
552         if( 0 == length $header) {
553             die 'header has length 0';
554         }
556         ##############
557         # check source
558         my $source_l = $msg_hash->{'source'};
559         if( 1 != @{$source_l} ) {
560             die 'no or more than 1 sources specified';
561         }
562         my $source = @{$source_l}[0];
563         if( 0 == length $source) {
564             die 'source has length 0';
565         }
566         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
567                 $source =~ /^GOSA$/i ) {
568             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
569         }
570         
571         ##############
572         # check target  
573         my $target_l = $msg_hash->{'target'};
574         if( 0 == @{$target_l} ) {
575             die "no targets specified";
576         }
577         foreach my $target (@$target_l) {
578             if( 0 == length $target) {
579                 die "target has length 0";
580             }
581             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582                     $target =~ /^GOSA$/i ||
583                     $target =~ /^\*$/ ||
584                     $target =~ /KNOWN_SERVER/i ||
585                     $target =~ /JOBDB/i ||
586                     $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 ){
587                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
588             }
589         }
590     };
591     if($@) {
592         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
593         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
594         $msg_hash = undef;
595     }
597     return ($msg_hash);
601 sub input_from_known_server {
602     my ($input, $remote_ip, $session_id) = @_ ;  
603     my ($msg, $msg_hash, $module);
605     my $sql_statement= "SELECT * FROM known_server";
606     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
608     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
609         my $host_name = $hit->{hostname};
610         if( not $host_name =~ "^$remote_ip") {
611             next;
612         }
613         my $host_key = $hit->{hostkey};
614         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
615         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
617         # check if module can open msg envelope with module key
618         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
619         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
620             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
621             daemon_log("$@", 8);
622             next;
623         }
624         else {
625             $msg = $tmp_msg;
626             $msg_hash = $tmp_msg_hash;
627             $module = "ServerPackages";
628             last;
629         }
630     }
632     if( (!$msg) || (!$msg_hash) || (!$module) ) {
633         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
634     }
635   
636     return ($msg, $msg_hash, $module);
640 sub input_from_known_client {
641     my ($input, $remote_ip, $session_id) = @_ ;  
642     my ($msg, $msg_hash, $module);
644     my $sql_statement= "SELECT * FROM known_clients";
645     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
646     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
647         my $host_name = $hit->{hostname};
648         if( not $host_name =~ /^$remote_ip:\d*$/) {
649                 next;
650                 }
651         my $host_key = $hit->{hostkey};
652         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
653         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
655         # check if module can open msg envelope with module key
656         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
658         if( (!$msg) || (!$msg_hash) ) {
659             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
660             &daemon_log("$@", 8);
661             next;
662         }
663         else {
664             $module = "ClientPackages";
665             last;
666         }
667     }
669     if( (!$msg) || (!$msg_hash) || (!$module) ) {
670         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
671     }
673     return ($msg, $msg_hash, $module);
677 sub input_from_unknown_host {
678     no strict "refs";
679     my ($input, $session_id) = @_ ;
680     my ($msg, $msg_hash, $module);
681     my $error_string;
682     
683         my %act_modules = %$known_modules;
684         
685     while( my ($mod, $info) = each(%act_modules)) {
687         # check a key exists for this module
688         my $module_key = ${$mod."_key"};
689         if( not defined $module_key ) {
690             if( $mod eq 'ArpHandler' ) {
691                 next;
692             }
693             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
694             next;
695         }
696         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
698         # check if module can open msg envelope with module key
699         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
700         if( (not defined $msg) || (not defined $msg_hash) ) {
701             next;
702         }
703         else {
704             $module = $mod;
705             last;
706         }
707     }
709     if( (!$msg) || (!$msg_hash) || (!$module)) {
710         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
711     }
713     return ($msg, $msg_hash, $module);
717 sub create_ciphering {
718     my ($passwd) = @_;
719         if((!defined($passwd)) || length($passwd)==0) {
720                 $passwd = "";
721         }
722     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
723     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
724     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
725     $my_cipher->set_iv($iv);
726     return $my_cipher;
730 sub encrypt_msg {
731     my ($msg, $key) = @_;
732     my $my_cipher = &create_ciphering($key);
733     my $len;
734     {
735             use bytes;
736             $len= 16-length($msg)%16;
737     }
738     $msg = "\0"x($len).$msg;
739     $msg = $my_cipher->encrypt($msg);
740     chomp($msg = &encode_base64($msg));
741     # there are no newlines allowed inside msg
742     $msg=~ s/\n//g;
743     return $msg;
747 sub decrypt_msg {
749     my ($msg, $key) = @_ ;
750     $msg = &decode_base64($msg);
751     my $my_cipher = &create_ciphering($key);
752     $msg = $my_cipher->decrypt($msg); 
753     $msg =~ s/\0*//g;
754     return $msg;
758 sub get_encrypt_key {
759     my ($target) = @_ ;
760     my $encrypt_key;
761     my $error = 0;
763     # target can be in known_server
764     if( not defined $encrypt_key ) {
765         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
766         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
767         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
768             my $host_name = $hit->{hostname};
769             if( $host_name ne $target ) {
770                 next;
771             }
772             $encrypt_key = $hit->{hostkey};
773             last;
774         }
775     }
777     # target can be in known_client
778     if( not defined $encrypt_key ) {
779         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
780         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
781         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
782             my $host_name = $hit->{hostname};
783             if( $host_name ne $target ) {
784                 next;
785             }
786             $encrypt_key = $hit->{hostkey};
787             last;
788         }
789     }
791     return $encrypt_key;
795 #===  FUNCTION  ================================================================
796 #         NAME:  open_socket
797 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
798 #                [PeerPort] string necessary if port not appended by PeerAddr
799 #      RETURNS:  socket IO::Socket::INET
800 #  DESCRIPTION:  open a socket to PeerAddr
801 #===============================================================================
802 sub open_socket {
803     my ($PeerAddr, $PeerPort) = @_ ;
804     if(defined($PeerPort)){
805         $PeerAddr = $PeerAddr.":".$PeerPort;
806     }
807     my $socket;
808     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
809             Porto => "tcp",
810             Type => SOCK_STREAM,
811             Timeout => 5,
812             );
813     if(not defined $socket) {
814         return;
815     }
816 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
817     return $socket;
821 #===  FUNCTION  ================================================================
822 #         NAME:  get_ip 
823 #   PARAMETERS:  interface name (i.e. eth0)
824 #      RETURNS:  (ip address) 
825 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
826 #===============================================================================
827 sub get_ip {
828         my $ifreq= shift;
829         my $result= "";
830         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
831         my $proto= getprotobyname('ip');
833         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
834                 or die "socket: $!";
836         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
837                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
838                 my ($port, $addr) = sockaddr_in $sin;
839                 my $ip            = inet_ntoa $addr;
841                 if ($ip && length($ip) > 0) {
842                         $result = $ip;
843                 }
844         }
846         return $result;
850 sub get_local_ip_for_remote_ip {
851         my $remote_ip= shift;
852         my $result="0.0.0.0";
854         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855                 if($remote_ip eq "127.0.0.1") {
856                         $result = "127.0.0.1";
857                 } else {
858                         my $PROC_NET_ROUTE= ('/proc/net/route');
860                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861                                 or die "Could not open $PROC_NET_ROUTE";
863                         my @ifs = <PROC_NET_ROUTE>;
865                         close(PROC_NET_ROUTE);
867                         # Eat header line
868                         shift @ifs;
869                         chomp @ifs;
870                         foreach my $line(@ifs) {
871                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872                                 my $destination;
873                                 my $mask;
874                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879                                         # destination matches route, save mac and exit
880                                         $result= &get_ip($Iface);
881                                         last;
882                                 }
883                         }
884                 }
885         } else {
886                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
887         }
888         return $result;
892 sub send_msg_to_target {
893     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894     my $error = 0;
895     my $header;
896     my $timestamp = &get_time();
897     my $new_status;
898     my $act_status;
899     my ($sql_statement, $res);
900   
901     if( $msg_header ) {
902         $header = "'$msg_header'-";
903     } else {
904         $header = "";
905     }
907         # Patch the source ip
908         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911         }
913     # encrypt xml msg
914     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916     # opensocket
917     my $socket = &open_socket($address);
918     if( !$socket ) {
919         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
920         $error++;
921     }
922     
923     if( $error == 0 ) {
924         # send xml msg
925         print $socket $crypted_msg."\n";
927         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928         daemon_log("$session_id DEBUG: message:\n$msg", 9);
929         
930     }
932     # close socket in any case
933     if( $socket ) {
934         close $socket;
935     }
937     if( $error > 0 ) { $new_status = "down"; }
938     else { $new_status = $msg_header; }
941     # known_clients
942     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
943     $res = $known_clients_db->select_dbentry($sql_statement);
944     if( keys(%$res) == 1) {
945         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
946         if ($act_status eq "down" && $new_status eq "down") {
947             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948             $res = $known_clients_db->del_dbentry($sql_statement);
949             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950         } else { 
951             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952             $res = $known_clients_db->update_dbentry($sql_statement);
953             if($new_status eq "down"){
954                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955             } else {
956                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957             }
958         }
959     }
961     # known_server
962     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963     $res = $known_server_db->select_dbentry($sql_statement);
964     if( keys(%$res) == 1) {
965         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
966         if ($act_status eq "down" && $new_status eq "down") {
967             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968             $res = $known_server_db->del_dbentry($sql_statement);
969             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970         } 
971         else { 
972             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973             $res = $known_server_db->update_dbentry($sql_statement);
974             if($new_status eq "down"){
975                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976             } else {
977                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978             }
979         }
980     }
981     return $error; 
985 sub update_jobdb_status_for_send_msgs {
986     my ($answer, $error) = @_;
987     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
988         my $jobdb_id = $1;
989             
990         # sending msg faild
991         if( $error ) {
992             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
993                 my $sql_statement = "UPDATE $job_queue_tn ".
994                     "SET status='error', result='can not deliver msg, please consult log file' ".
995                     "WHERE id=$jobdb_id";
996                 my $res = $job_db->update_dbentry($sql_statement);
997             }
999         # sending msg was successful
1000         } else {
1001             my $sql_statement = "UPDATE $job_queue_tn ".
1002                 "SET status='done' ".
1003                 "WHERE id=$jobdb_id AND status='processed'";
1004             my $res = $job_db->update_dbentry($sql_statement);
1005         }
1006     }
1010 sub sig_handler {
1011         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1012         daemon_log("0 INFO got signal '$signal'", 1); 
1013         $kernel->sig_handled();
1014         return;
1018 sub msg_to_decrypt {
1019     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1020     my $session_id = $session->ID;
1021     my ($msg, $msg_hash, $module);
1022     my $error = 0;
1024     # hole neue msg aus @msgs_to_decrypt
1025     my $next_msg = shift @msgs_to_decrypt;
1026     
1027     # entschlüssle sie
1029     # msg is from a new client or gosa
1030     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1031     # msg is from a gosa-si-server or gosa-si-bus
1032     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1034     }
1035     # msg is from a gosa-si-client
1036     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1038     }
1039     # an error occurred
1040     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1042         # could not understand a msg from its server the client cause a re-registering process
1043         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1044                 "' to cause a re-registering of the client if necessary", 5);
1045         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1047         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1048             my $host_name = $hit->{'hostname'};
1049             my $host_key = $hit->{'hostkey'};
1050             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053         }
1054         $error++;
1055     }
1056     
1057     my $header;
1058     my $target;
1059     my $done = 0;
1060     my $sql;
1061     my $res;
1062     # check whether this message should be processed here
1063     if ($error == 0) {
1064         $header = @{$msg_hash->{'header'}}[0];
1065         $target = @{$msg_hash->{'target'}}[0];
1067         # target is own address without forward_to_gosa-tag -> process here
1068         if (not $done) {
1069             if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1070                 $done = 1;
1071                 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1072             }
1073         }
1075         # target is a client address in known_clients -> process here
1076         if (not $done) {
1077             $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1078             $res = $known_clients_db->select_dbentry($sql);
1079             if (keys(%$res) > 0) {
1080                 $done = 1; 
1081                 my $hostname = $res->{1}->{'hostname'};
1082                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1083                 print STDERR "target is a client address in known_clients -> process here\n";
1084             }
1085         }
1087         # if message should be processed here -> add message to incoming_db
1088         if ($done) {
1089             my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1090                     primkey=>[],
1091                     headertag=>$header,
1092                     targettag=>$target,
1093                     xmlmessage=>$msg,
1094                     timestamp=>&get_time,
1095                     module=>$module,
1096                     sessionid=>$session_id,
1097                     } );
1099         }
1101         # target is own address with forward_to_gosa-tag -> forward to gosa
1102         if (not $done) {
1103             if (($target eq $server_address) && (exists $msg_hash->{'forward_to_gosa'})){
1104                 if( $msg =~ s/session_id=(\d+)$// ) {
1105                     my $session_id = $1 ;
1106                 }
1107                 if( defined $session_id ) {
1108                     my $session_reference = $kernel->ID_id_to_session($session_id);
1109                     if( defined $session_reference ) {
1110                         $heap = $session_reference->get_heap();
1111                     }
1112                 }
1113                 if(exists $heap->{'client'}) {
1114                     $msg = &encrypt_msg($msg, $GosaPackages_key);
1115                     $heap->{'client'}->put($msg);
1116                 }
1117                 $done = 1;
1118                 print STDERR "target is own address with forward_to_gosa-tag -> forward to gosa\n";
1119             }
1121         }
1123         # target is a client address in foreign_clients -> forward to registration server
1124         if (not $done) {
1125             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1126             $res = $foreign_clients_db->select_dbentry($sql);
1127             if (keys(%$res) > 0) {
1128                 my $hostname = $res->{1}->{'hostname'};
1129                 my $regserver = $res->{1}->{'regserver'};
1130                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1131                 my $res = $known_server_db->select_dbentry($sql);
1132                 if (keys(%$res) > 0) {
1133                     my $regserver_key = $res->{1}->{'hostkey'};
1134                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1135                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1136                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1137                 }
1138                 $done = 1;
1139                 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1140             }
1141         }
1143         # target is a server address -> forward to server
1144         if (not $done) {
1145             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1146             $res = $known_server_db->select_dbentry($sql);
1147             if (keys(%$res) > 0) {
1148                 my $hostkey = $res->{1}->{'hostkey'};
1149                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1150                 $done = 1;
1151                 print STDERR "target is a server address -> forward to server\n";
1152             }
1153         }
1156     }
1157     return;
1161 sub next_task {
1162     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1163     my $running_task = POE::Wheel::Run->new(
1164             Program => sub { process_task($session, $heap, $task) },
1165             StdioFilter => POE::Filter::Reference->new(),
1166             StdoutEvent  => "task_result",
1167             StderrEvent  => "task_debug",
1168             CloseEvent   => "task_done",
1169             );
1170     $heap->{task}->{ $running_task->ID } = $running_task;
1173 sub handle_task_result {
1174     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1175     my $client_answer = $result->{'answer'};
1176     if( $client_answer =~ s/session_id=(\d+)$// ) {
1177         my $session_id = $1;
1178         if( defined $session_id ) {
1179             my $session_reference = $kernel->ID_id_to_session($session_id);
1180             if( defined $session_reference ) {
1181                 $heap = $session_reference->get_heap();
1182             }
1183         }
1185         if(exists $heap->{'client'}) {
1186             $heap->{'client'}->put($client_answer);
1187         }
1188     }
1189     $kernel->sig(CHLD => "child_reap");
1192 sub handle_task_debug {
1193     my $result = $_[ARG0];
1194     print STDERR "$result\n";
1197 sub handle_task_done {
1198     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1199     delete $heap->{task}->{$task_id};
1202 sub process_task {
1203     no strict "refs";
1204     my ($session, $heap, $task) = @_;
1205     my $error = 0;
1206     my $answer_l;
1207     my ($answer_header, @answer_target_l, $answer_source);
1208     my $client_answer = "";
1210     # prepare all variables needed to process message
1211     my $msg = $task->{'xmlmessage'};
1212     my $incoming_id = $task->{'id'};
1213     my $module = $task->{'module'};
1214     my $header =  $task->{'headertag'};
1215     my $session_id = $task->{'sessionid'};
1216     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1217     my $source = @{$msg_hash->{'source'}}[0];
1218     
1219     # set timestamp of incoming client uptodate, so client will not 
1220     # be deleted from known_clients because of expiration
1221     my $act_time = &get_time();
1222     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1223     my $res = $known_clients_db->exec_statement($sql);
1225     ######################
1226     # process incoming msg
1227     if( $error == 0) {
1228         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1229         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1230         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1232         if ( 0 < @{$answer_l} ) {
1233             my $answer_str = join("\n", @{$answer_l});
1234             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1235                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1236             }
1237             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1238         } else {
1239             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1240         }
1242     }
1243     if( !$answer_l ) { $error++ };
1245     ########
1246     # answer
1247     if( $error == 0 ) {
1249         foreach my $answer ( @{$answer_l} ) {
1250             # check outgoing msg to xml validity
1251             my $answer_hash = &check_outgoing_xml_validity($answer);
1252             if( not defined $answer_hash ) { next; }
1253             
1254             $answer_header = @{$answer_hash->{'header'}}[0];
1255             @answer_target_l = @{$answer_hash->{'target'}};
1256             $answer_source = @{$answer_hash->{'source'}}[0];
1258             # deliver msg to all targets 
1259             foreach my $answer_target ( @answer_target_l ) {
1261                 # targets of msg are all gosa-si-clients in known_clients_db
1262                 if( $answer_target eq "*" ) {
1263                     # answer is for all clients
1264                     my $sql_statement= "SELECT * FROM known_clients";
1265                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1266                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1267                         my $host_name = $hit->{hostname};
1268                         my $host_key = $hit->{hostkey};
1269                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1270                         &update_jobdb_status_for_send_msgs($answer, $error);
1271                     }
1272                 }
1274                 # targets of msg are all gosa-si-server in known_server_db
1275                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1276                     # answer is for all server in known_server
1277                     my $sql_statement= "SELECT * FROM $known_server_tn";
1278                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1279                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1280                         my $host_name = $hit->{hostname};
1281                         my $host_key = $hit->{hostkey};
1282                         $answer =~ s/<target>KNOWN_SERVER<\/target>/<target>$host_name<\/target>/g;
1283                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1284                         &update_jobdb_status_for_send_msgs($answer, $error);
1285                     }
1286                 }
1288                 # target of msg is GOsa
1289                                 elsif( $answer_target eq "GOSA" ) {
1290                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1291                                         my $add_on = "";
1292                     if( defined $session_id ) {
1293                         $add_on = ".session_id=$session_id";
1294                     }
1295                     # answer is for GOSA and has to returned to connected client
1296                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1297                     $client_answer = $gosa_answer.$add_on;
1298                 }
1300                 # target of msg is job queue at this host
1301                 elsif( $answer_target eq "JOBDB") {
1302                     $answer =~ /<header>(\S+)<\/header>/;   
1303                     my $header;
1304                     if( defined $1 ) { $header = $1; }
1305                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1306                     &update_jobdb_status_for_send_msgs($answer, $error);
1307                 }
1309                 # target of msg is a mac address
1310                 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 ) {
1311                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1312                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1313                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1314                     my $found_ip_flag = 0;
1315                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1316                         my $host_name = $hit->{hostname};
1317                         my $host_key = $hit->{hostkey};
1318                         $answer =~ s/$answer_target/$host_name/g;
1319                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1320                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1321                         &update_jobdb_status_for_send_msgs($answer, $error);
1322                         $found_ip_flag++ ;
1323                     }   
1324                     if( $found_ip_flag == 0) {
1325                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1326                         if( $bus_activ eq "true" ) { 
1327                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1328                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1329                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1330                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1331                                 my $bus_address = $hit->{hostname};
1332                                 my $bus_key = $hit->{hostkey};
1333                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1334                                 &update_jobdb_status_for_send_msgs($answer, $error);
1335                                 last;
1336                             }
1337                         }
1339                     }
1341                 #  answer is for one specific host   
1342                 } else {
1343                     # get encrypt_key
1344                     my $encrypt_key = &get_encrypt_key($answer_target);
1345                     if( not defined $encrypt_key ) {
1346                         # unknown target, forward msg to bus
1347                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1348                         if( $bus_activ eq "true" ) { 
1349                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1350                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1351                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1352                             my $res_length = keys( %{$query_res} );
1353                             if( $res_length == 0 ){
1354                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1355                                         "no bus found in known_server", 3);
1356                             }
1357                             else {
1358                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1359                                     my $bus_key = $hit->{hostkey};
1360                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1361                                     &update_jobdb_status_for_send_msgs($answer, $error);
1362                                 }
1363                             }
1364                         }
1365                         next;
1366                     }
1367                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1368                     &update_jobdb_status_for_send_msgs($answer, $error);
1369                 }
1370             }
1371         }
1372     }
1374     my $filter = POE::Filter::Reference->new();
1375     my %result = ( 
1376             status => "seems ok to me",
1377             answer => $client_answer,
1378             );
1380     my $output = $filter->put( [ \%result ] );
1381     print @$output;
1386 sub session_start {
1387     my ($kernel) = $_[KERNEL];
1388     &trigger_db_loop($kernel);
1389     $global_kernel = $kernel;
1390     $kernel->yield('register_at_foreign_servers');
1391         $kernel->yield('create_fai_server_db', $fai_server_tn );
1392         $kernel->yield('create_fai_release_db', $fai_release_tn );
1393     $kernel->yield('watch_for_next_tasks');
1394         $kernel->sig(USR1 => "sig_handler");
1395         $kernel->sig(USR2 => "create_packages_list_db");
1396         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1397         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1398         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1399     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1400         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1401     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1405 sub trigger_db_loop {
1406         my ($kernel) = @_ ;
1407 #       $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1408 #       $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1409 #       $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1410 #    $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1411 #       $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1412 #    $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1416 sub watch_for_done_jobs {
1417     my ($kernel,$heap) = @_[KERNEL, HEAP];
1419     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1420         my $res = $job_db->select_dbentry( $sql_statement );
1422     while( my ($id, $hit) = each %{$res} ) {
1423         my $jobdb_id = $hit->{id};
1424         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1425         my $res = $job_db->del_dbentry($sql_statement); 
1426     }
1428     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1432 sub watch_for_new_jobs {
1433         if($watch_for_new_jobs_in_progress == 0) {
1434                 $watch_for_new_jobs_in_progress = 1;
1435                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1437                 # check gosa job queue for jobs with executable timestamp
1438                 my $timestamp = &get_time();
1439                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1440                 my $res = $job_db->exec_statement( $sql_statement );
1442                 # Merge all new jobs that would do the same actions
1443                 my @drops;
1444                 my $hits;
1445                 foreach my $hit (reverse @{$res} ) {
1446                         my $macaddress= lc @{$hit}[8];
1447                         my $headertag= @{$hit}[5];
1448                         if(
1449                                 defined($hits->{$macaddress}) &&
1450                                 defined($hits->{$macaddress}->{$headertag}) &&
1451                                 defined($hits->{$macaddress}->{$headertag}[0])
1452                         ) {
1453                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1454                         }
1455                         $hits->{$macaddress}->{$headertag}= $hit;
1456                 }
1458                 # Delete new jobs with a matching job in state 'processing'
1459                 foreach my $macaddress (keys %{$hits}) {
1460                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1461                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1462                                 if(defined($jobdb_id)) {
1463                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1464                                         my $res = $job_db->exec_statement( $sql_statement );
1465                                         foreach my $hit (@{$res}) {
1466                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1467                                         }
1468                                 } else {
1469                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1470                                 }
1471                         }
1472                 }
1474                 # Commit deletion
1475                 $job_db->exec_statementlist(\@drops);
1477                 # Look for new jobs that could be executed
1478                 foreach my $macaddress (keys %{$hits}) {
1480                         # Look if there is an executing job
1481                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1482                         my $res = $job_db->exec_statement( $sql_statement );
1484                         # Skip new jobs for host if there is a processing job
1485                         if(defined($res) and defined @{$res}[0]) {
1486                                 next;
1487                         }
1489                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1490                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1491                                 if(defined($jobdb_id)) {
1492                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1494                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1495                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1496                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1498                                         # expect macaddress is unique!!!!!!
1499                                         my $target = $res_hash->{1}->{hostname};
1501                                         # change header
1502                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1504                                         # add sqlite_id
1505                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1507                                         $job_msg =~ /<header>(\S+)<\/header>/;
1508                                         my $header = $1 ;
1509                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1511                                         # update status in job queue to 'processing'
1512                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1513                                         my $res = $job_db->update_dbentry($sql_statement);
1514 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1516                                         # We don't want parallel processing
1517                                         last;
1518                                 }
1519                         }
1520                 }
1522                 $watch_for_new_jobs_in_progress = 0;
1523                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1524         }
1528 sub watch_for_new_messages {
1529     my ($kernel,$heap) = @_[KERNEL, HEAP];
1530     my @coll_user_msg;   # collection list of outgoing messages
1531     
1532     # check messaging_db for new incoming messages with executable timestamp
1533     my $timestamp = &get_time();
1534     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1535     my $res = $messaging_db->exec_statement( $sql_statement );
1536         foreach my $hit (@{$res}) {
1538         # create outgoing messages
1539         my $message_to = @{$hit}[3];
1540         # translate message_to to plain login name
1541         my @message_to_l = split(/,/, $message_to);  
1542                 my %receiver_h; 
1543                 foreach my $receiver (@message_to_l) {
1544                         if ($receiver =~ /^u_([\s\S]*)$/) {
1545                                 $receiver_h{$1} = 0;
1546                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1547                                 my $group_name = $1;
1548                                 # fetch all group members from ldap and add them to receiver hash
1549                                 my $ldap_handle = &get_ldap_handle();
1550                                 if (defined $ldap_handle) {
1551                                                 my $mesg = $ldap_handle->search(
1552                                                                                 base => $ldap_base,
1553                                                                                 scope => 'sub',
1554                                                                                 attrs => ['memberUid'],
1555                                                                                 filter => "cn=$group_name",
1556                                                                                 );
1557                                                 if ($mesg->count) {
1558                                                                 my @entries = $mesg->entries;
1559                                                                 foreach my $entry (@entries) {
1560                                                                                 my @receivers= $entry->get_value("memberUid");
1561                                                                                 foreach my $receiver (@receivers) { 
1562                                                                                                 $receiver_h{$1} = 0;
1563                                                                                 }
1564                                                                 }
1565                                                 } 
1566                                                 # translating errors ?
1567                                                 if ($mesg->code) {
1568                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1569                                                 }
1570                                 # ldap handle error ?           
1571                                 } else {
1572                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1573                                 }
1574                         } else {
1575                                 my $sbjct = &encode_base64(@{$hit}[1]);
1576                                 my $msg = &encode_base64(@{$hit}[7]);
1577                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1578                         }
1579                 }
1580                 my @receiver_l = keys(%receiver_h);
1582         my $message_id = @{$hit}[0];
1584         #add each outgoing msg to messaging_db
1585         my $receiver;
1586         foreach $receiver (@receiver_l) {
1587             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1588                 "VALUES ('".
1589                 $message_id."', '".    # id
1590                 @{$hit}[1]."', '".     # subject
1591                 @{$hit}[2]."', '".     # message_from
1592                 $receiver."', '".      # message_to
1593                 "none"."', '".         # flag
1594                 "out"."', '".          # direction
1595                 @{$hit}[6]."', '".     # delivery_time
1596                 @{$hit}[7]."', '".     # message
1597                 $timestamp."'".     # timestamp
1598                 ")";
1599             &daemon_log("M DEBUG: $sql_statement", 1);
1600             my $res = $messaging_db->exec_statement($sql_statement);
1601             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1602         }
1604         # set incoming message to flag d=deliverd
1605         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1606         &daemon_log("M DEBUG: $sql_statement", 7);
1607         $res = $messaging_db->update_dbentry($sql_statement);
1608         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1609     }
1611     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1612     return;
1615 sub watch_for_delivery_messages {
1616     my ($kernel, $heap) = @_[KERNEL, HEAP];
1618     # select outgoing messages
1619     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1620     #&daemon_log("0 DEBUG: $sql", 7);
1621     my $res = $messaging_db->exec_statement( $sql_statement );
1622     
1623     # build out msg for each    usr
1624     foreach my $hit (@{$res}) {
1625         my $receiver = @{$hit}[3];
1626         my $msg_id = @{$hit}[0];
1627         my $subject = @{$hit}[1];
1628         my $message = @{$hit}[7];
1630         # resolve usr -> host where usr is logged in
1631         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1632         #&daemon_log("0 DEBUG: $sql", 7);
1633         my $res = $login_users_db->exec_statement($sql);
1635         # reciver is logged in nowhere
1636         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1638                 my $send_succeed = 0;
1639                 foreach my $hit (@$res) {
1640                                 my $receiver_host = @$hit[0];
1641                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1643                                 # fetch key to encrypt msg propperly for usr/host
1644                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1645                                 &daemon_log("0 DEBUG: $sql", 7);
1646                                 my $res = $known_clients_db->select_dbentry($sql);
1648                                 # host is already down
1649                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1651                                 # host is on
1652                                 my $receiver_key = @{@{$res}[0]}[2];
1653                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1654                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1655                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1656                                 if ($error == 0 ) {
1657                                         $send_succeed++ ;
1658                                 }
1659                 }
1661                 if ($send_succeed) {
1662                                 # set outgoing msg at db to deliverd
1663                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1664                                 &daemon_log("0 DEBUG: $sql", 7);
1665                                 my $res = $messaging_db->exec_statement($sql); 
1666                 }
1667         }
1669     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1670     return;
1674 sub watch_for_done_messages {
1675     my ($kernel,$heap) = @_[KERNEL, HEAP];
1677     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1678     #&daemon_log("0 DEBUG: $sql", 7);
1679     my $res = $messaging_db->exec_statement($sql); 
1681     foreach my $hit (@{$res}) {
1682         my $msg_id = @{$hit}[0];
1684         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1685         #&daemon_log("0 DEBUG: $sql", 7); 
1686         my $res = $messaging_db->exec_statement($sql);
1688         # not all usr msgs have been seen till now
1689         if ( ref(@$res[0]) eq "ARRAY") { next; }
1690         
1691         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1692         #&daemon_log("0 DEBUG: $sql", 7);
1693         $res = $messaging_db->exec_statement($sql);
1694     
1695     }
1697     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1698     return;
1702 sub watch_for_old_known_clients {
1703     my ($kernel,$heap) = @_[KERNEL, HEAP];
1705     my $sql_statement = "SELECT * FROM $known_clients_tn";
1706     my $res = $known_clients_db->select_dbentry( $sql_statement );
1708     my $act_time = int(&get_time());
1710     while ( my ($hit_num, $hit) = each %$res) {
1711         my $expired_timestamp = int($hit->{'timestamp'});
1712         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1713         my $dt = DateTime->new( year   => $1,
1714                 month  => $2,
1715                 day    => $3,
1716                 hour   => $4,
1717                 minute => $5,
1718                 second => $6,
1719                 );
1721         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1722         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1723         if ($act_time > $expired_timestamp) {
1724             my $hostname = $hit->{'hostname'};
1725             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1726             my $del_res = $known_clients_db->exec_statement($del_sql);
1728             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1729         }
1731     }
1733     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1737 sub watch_for_next_tasks {
1738     my ($kernel,$heap) = @_[KERNEL, HEAP];
1740     my $sql = "SELECT * FROM $incoming_tn";
1741     my $res = $incoming_db->select_dbentry($sql);
1743     while ( my ($hit_num, $hit) = each %$res) {
1744         my $headertag = $hit->{'headertag'};
1745         if ($headertag =~ /^answer_(\d+)/) {
1746             # do not start processing, this message is for a still running POE::Wheel
1747             next;
1748         }
1749         my $message_id = $hit->{'id'};
1750         $kernel->yield('next_task', $hit);
1752         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1753         my $res = $incoming_db->exec_statement($sql);
1754     }
1756     $kernel->delay_set('watch_for_next_tasks', 1); 
1760 sub get_ldap_handle {
1761         my ($session_id) = @_;
1762         my $heap;
1763         my $ldap_handle;
1765         if (not defined $session_id ) { $session_id = 0 };
1766         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1768         if ($session_id == 0) {
1769                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1770                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1771                 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password); 
1773         } else {
1774                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1775                 if( defined $session_reference ) {
1776                         $heap = $session_reference->get_heap();
1777                 }
1779                 if (not defined $heap) {
1780                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1781                         return;
1782                 }
1784                 # TODO: This "if" is nonsense, because it doesn't prove that the
1785                 #       used handle is still valid - or if we've to reconnect...
1786                 #if (not exists $heap->{ldap_handle}) {
1787                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1788                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1789                         $heap->{ldap_handle} = $ldap_handle;
1790                 #}
1791         }
1792         return $ldap_handle;
1796 sub change_fai_state {
1797     my ($st, $targets, $session_id) = @_;
1798     $session_id = 0 if not defined $session_id;
1799     # Set FAI state to localboot
1800     my %mapActions= (
1801         reboot    => '',
1802         update    => 'softupdate',
1803         localboot => 'localboot',
1804         reinstall => 'install',
1805         rescan    => '',
1806         wake      => '',
1807         memcheck  => 'memcheck',
1808         sysinfo   => 'sysinfo',
1809         install   => 'install',
1810     );
1812     # Return if this is unknown
1813     if (!exists $mapActions{ $st }){
1814         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1815       return;
1816     }
1818     my $state= $mapActions{ $st };
1820     my $ldap_handle = &get_ldap_handle($session_id);
1821     if( defined($ldap_handle) ) {
1823       # Build search filter for hosts
1824         my $search= "(&(objectClass=GOhard)";
1825         foreach (@{$targets}){
1826             $search.= "(macAddress=$_)";
1827         }
1828         $search.= ")";
1830       # If there's any host inside of the search string, procress them
1831         if (!($search =~ /macAddress/)){
1832             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1833             return;
1834         }
1836       # Perform search for Unit Tag
1837       my $mesg = $ldap_handle->search(
1838           base   => $ldap_base,
1839           scope  => 'sub',
1840           attrs  => ['dn', 'FAIstate', 'objectClass'],
1841           filter => "$search"
1842           );
1844           if ($mesg->count) {
1845                   my @entries = $mesg->entries;
1846                   foreach my $entry (@entries) {
1847                           # Only modify entry if it is not set to '$state'
1848                           if ($entry->get_value("FAIstate") ne "$state"){
1849                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1850                                   my $result;
1851                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1852                                   if (exists $tmp{'FAIobject'}){
1853                                           if ($state eq ''){
1854                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1855                                                           delete => [ FAIstate => [] ] ]);
1856                                           } else {
1857                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1858                                                           replace => [ FAIstate => $state ] ]);
1859                                           }
1860                                   } elsif ($state ne ''){
1861                                           $result= $ldap_handle->modify($entry->dn, changes => [
1862                                                   add     => [ objectClass => 'FAIobject' ],
1863                                                   add     => [ FAIstate => $state ] ]);
1864                                   }
1866                                   # Errors?
1867                                   if ($result->code){
1868                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1869                                   }
1870                           } else {
1871                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1872                           }  
1873                   }
1874           }
1875     # if no ldap handle defined
1876     } else {
1877         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1878     }
1883 sub change_goto_state {
1884     my ($st, $targets, $session_id) = @_;
1885     $session_id = 0  if not defined $session_id;
1887     # Switch on or off?
1888     my $state= $st eq 'active' ? 'active': 'locked';
1890     my $ldap_handle = &get_ldap_handle($session_id);
1891     if( defined($ldap_handle) ) {
1893       # Build search filter for hosts
1894       my $search= "(&(objectClass=GOhard)";
1895       foreach (@{$targets}){
1896         $search.= "(macAddress=$_)";
1897       }
1898       $search.= ")";
1900       # If there's any host inside of the search string, procress them
1901       if (!($search =~ /macAddress/)){
1902         return;
1903       }
1905       # Perform search for Unit Tag
1906       my $mesg = $ldap_handle->search(
1907           base   => $ldap_base,
1908           scope  => 'sub',
1909           attrs  => ['dn', 'gotoMode'],
1910           filter => "$search"
1911           );
1913       if ($mesg->count) {
1914         my @entries = $mesg->entries;
1915         foreach my $entry (@entries) {
1917           # Only modify entry if it is not set to '$state'
1918           if ($entry->get_value("gotoMode") ne $state){
1920             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1921             my $result;
1922             $result= $ldap_handle->modify($entry->dn, changes => [
1923                                                 replace => [ gotoMode => $state ] ]);
1925             # Errors?
1926             if ($result->code){
1927               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1928             }
1930           }
1931         }
1932       }
1934     }
1938 sub run_create_fai_server_db {
1939     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1940     my $session_id = $session->ID;
1941     my $task = POE::Wheel::Run->new(
1942             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1943             StdoutEvent  => "session_run_result",
1944             StderrEvent  => "session_run_debug",
1945             CloseEvent   => "session_run_done",
1946             );
1948     $heap->{task}->{ $task->ID } = $task;
1949     return;
1953 sub create_fai_server_db {
1954     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1955         my $result;
1957         if (not defined $session_id) { $session_id = 0; }
1958     my $ldap_handle = &get_ldap_handle();
1959         if(defined($ldap_handle)) {
1960                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1961                 my $mesg= $ldap_handle->search(
1962                         base   => $ldap_base,
1963                         scope  => 'sub',
1964                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1965                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1966                 );
1967                 if($mesg->{'resultCode'} == 0 &&
1968                    $mesg->count != 0) {
1969                    foreach my $entry (@{$mesg->{entries}}) {
1970                            if($entry->exists('FAIrepository')) {
1971                                    # Add an entry for each Repository configured for server
1972                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1973                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1974                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1975                                                    $result= $fai_server_db->add_dbentry( { 
1976                                                                    table => $table_name,
1977                                                                    primkey => ['server', 'release', 'tag'],
1978                                                                    server => $tmp_url,
1979                                                                    release => $tmp_release,
1980                                                                    sections => $tmp_sections,
1981                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1982                                                            } );
1983                                            }
1984                                    }
1985                            }
1986                    }
1987                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1989                 # TODO: Find a way to post the 'create_packages_list_db' event
1990                 if(not defined($dont_create_packages_list)) {
1991                         &create_packages_list_db(undef, undef, $session_id);
1992                 }
1993         }       
1994     
1995     $ldap_handle->disconnect;
1996         return $result;
2000 sub run_create_fai_release_db {
2001     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2002         my $session_id = $session->ID;
2003     my $task = POE::Wheel::Run->new(
2004             Program => sub { &create_fai_release_db($table_name, $session_id) },
2005             StdoutEvent  => "session_run_result",
2006             StderrEvent  => "session_run_debug",
2007             CloseEvent   => "session_run_done",
2008             );
2010     $heap->{task}->{ $task->ID } = $task;
2011     return;
2015 sub create_fai_release_db {
2016         my ($table_name, $session_id) = @_;
2017         my $result;
2019     # used for logging
2020     if (not defined $session_id) { $session_id = 0; }
2022     my $ldap_handle = &get_ldap_handle();
2023         if(defined($ldap_handle)) {
2024                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2025                 my $mesg= $ldap_handle->search(
2026                         base   => $ldap_base,
2027                         scope  => 'sub',
2028                         attrs  => [],
2029                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2030                 );
2031                 if($mesg->{'resultCode'} == 0 &&
2032                         $mesg->count != 0) {
2033                         # Walk through all possible FAI container ou's
2034                         my @sql_list;
2035                         my $timestamp= &get_time();
2036                         foreach my $ou (@{$mesg->{entries}}) {
2037                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2038                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2039                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2040                                         if(@tmp_array) {
2041                                                 foreach my $entry (@tmp_array) {
2042                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2043                                                                 my $sql= 
2044                                                                 "INSERT INTO $table_name "
2045                                                                 ."(timestamp, release, class, type, state) VALUES ("
2046                                                                 .$timestamp.","
2047                                                                 ."'".$entry->{'release'}."',"
2048                                                                 ."'".$entry->{'class'}."',"
2049                                                                 ."'".$entry->{'type'}."',"
2050                                                                 ."'".$entry->{'state'}."')";
2051                                                                 push @sql_list, $sql;
2052                                                         }
2053                                                 }
2054                                         }
2055                                 }
2056                         }
2058                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2059                         if(@sql_list) {
2060                                 unshift @sql_list, "VACUUM";
2061                                 unshift @sql_list, "DELETE FROM $table_name";
2062                                 $fai_release_db->exec_statementlist(\@sql_list);
2063                         }
2064                         daemon_log("$session_id DEBUG: Done with inserting",7);
2065                 }
2066                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2067         }
2068     $ldap_handle->disconnect;
2069         return $result;
2072 sub get_fai_types {
2073         my $tmp_classes = shift || return undef;
2074         my @result;
2076         foreach my $type(keys %{$tmp_classes}) {
2077                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2078                         my $entry = {
2079                                 type => $type,
2080                                 state => $tmp_classes->{$type}[0],
2081                         };
2082                         push @result, $entry;
2083                 }
2084         }
2086         return @result;
2089 sub get_fai_state {
2090         my $result = "";
2091         my $tmp_classes = shift || return $result;
2093         foreach my $type(keys %{$tmp_classes}) {
2094                 if(defined($tmp_classes->{$type}[0])) {
2095                         $result = $tmp_classes->{$type}[0];
2096                         
2097                 # State is equal for all types in class
2098                         last;
2099                 }
2100         }
2102         return $result;
2105 sub resolve_fai_classes {
2106         my ($fai_base, $ldap_handle, $session_id) = @_;
2107         if (not defined $session_id) { $session_id = 0; }
2108         my $result;
2109         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2110         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2111         my $fai_classes;
2113         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2114         my $mesg= $ldap_handle->search(
2115                 base   => $fai_base,
2116                 scope  => 'sub',
2117                 attrs  => ['cn','objectClass','FAIstate'],
2118                 filter => $fai_filter,
2119         );
2120         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2122         if($mesg->{'resultCode'} == 0 &&
2123                 $mesg->count != 0) {
2124                 foreach my $entry (@{$mesg->{entries}}) {
2125                         if($entry->exists('cn')) {
2126                                 my $tmp_dn= $entry->dn();
2128                                 # Skip classname and ou dn parts for class
2129                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2131                                 # Skip classes without releases
2132                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2133                                         next;
2134                                 }
2136                                 my $tmp_cn= $entry->get_value('cn');
2137                                 my $tmp_state= $entry->get_value('FAIstate');
2139                                 my $tmp_type;
2140                                 # Get FAI type
2141                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2142                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2143                                                 $tmp_type= $oclass;
2144                                                 last;
2145                                         }
2146                                 }
2148                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2149                                         # A Subrelease
2150                                         my @sub_releases = split(/,/, $tmp_release);
2152                                         # Walk through subreleases and build hash tree
2153                                         my $hash;
2154                                         while(my $tmp_sub_release = pop @sub_releases) {
2155                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2156                                         }
2157                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2158                                 } else {
2159                                         # A branch, no subrelease
2160                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2161                                 }
2162                         } elsif (!$entry->exists('cn')) {
2163                                 my $tmp_dn= $entry->dn();
2164                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2166                                 # Skip classes without releases
2167                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2168                                         next;
2169                                 }
2171                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2172                                         # A Subrelease
2173                                         my @sub_releases= split(/,/, $tmp_release);
2175                                         # Walk through subreleases and build hash tree
2176                                         my $hash;
2177                                         while(my $tmp_sub_release = pop @sub_releases) {
2178                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2179                                         }
2180                                         # Remove the last two characters
2181                                         chop($hash);
2182                                         chop($hash);
2184                                         eval('$fai_classes->'.$hash.'= {}');
2185                                 } else {
2186                                         # A branch, no subrelease
2187                                         if(!exists($fai_classes->{$tmp_release})) {
2188                                                 $fai_classes->{$tmp_release} = {};
2189                                         }
2190                                 }
2191                         }
2192                 }
2194                 # The hash is complete, now we can honor the copy-on-write based missing entries
2195                 foreach my $release (keys %$fai_classes) {
2196                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2197                 }
2198         }
2199         return $result;
2202 sub apply_fai_inheritance {
2203        my $fai_classes = shift || return {};
2204        my $tmp_classes;
2206        # Get the classes from the branch
2207        foreach my $class (keys %{$fai_classes}) {
2208                # Skip subreleases
2209                if($class =~ /^ou=.*$/) {
2210                        next;
2211                } else {
2212                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2213                }
2214        }
2216        # Apply to each subrelease
2217        foreach my $subrelease (keys %{$fai_classes}) {
2218                if($subrelease =~ /ou=/) {
2219                        foreach my $tmp_class (keys %{$tmp_classes}) {
2220                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2221                                        $fai_classes->{$subrelease}->{$tmp_class} =
2222                                        deep_copy($tmp_classes->{$tmp_class});
2223                                } else {
2224                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2225                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2226                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2227                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2228                                                }
2229                                        }
2230                                }
2231                        }
2232                }
2233        }
2235        # Find subreleases in deeper levels
2236        foreach my $subrelease (keys %{$fai_classes}) {
2237                if($subrelease =~ /ou=/) {
2238                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2239                                if($subsubrelease =~ /ou=/) {
2240                                        apply_fai_inheritance($fai_classes->{$subrelease});
2241                                }
2242                        }
2243                }
2244        }
2246        return $fai_classes;
2249 sub get_fai_release_entries {
2250         my $tmp_classes = shift || return;
2251         my $parent = shift || "";
2252         my @result = shift || ();
2254         foreach my $entry (keys %{$tmp_classes}) {
2255                 if(defined($entry)) {
2256                         if($entry =~ /^ou=.*$/) {
2257                                 my $release_name = $entry;
2258                                 $release_name =~ s/ou=//g;
2259                                 if(length($parent)>0) {
2260                                         $release_name = $parent."/".$release_name;
2261                                 }
2262                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2263                                 foreach my $bufentry(@bufentries) {
2264                                         push @result, $bufentry;
2265                                 }
2266                         } else {
2267                                 my @types = get_fai_types($tmp_classes->{$entry});
2268                                 foreach my $type (@types) {
2269                                         push @result, 
2270                                         {
2271                                                 'class' => $entry,
2272                                                 'type' => $type->{'type'},
2273                                                 'release' => $parent,
2274                                                 'state' => $type->{'state'},
2275                                         };
2276                                 }
2277                         }
2278                 }
2279         }
2281         return @result;
2284 sub deep_copy {
2285         my $this = shift;
2286         if (not ref $this) {
2287                 $this;
2288         } elsif (ref $this eq "ARRAY") {
2289                 [map deep_copy($_), @$this];
2290         } elsif (ref $this eq "HASH") {
2291                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2292         } else { die "what type is $_?" }
2296 sub session_run_result {
2297     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2298     $kernel->sig(CHLD => "child_reap");
2301 sub session_run_debug {
2302     my $result = $_[ARG0];
2303     print STDERR "$result\n";
2306 sub session_run_done {
2307     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2308     delete $heap->{task}->{$task_id};
2312 sub create_sources_list {
2313         my $session_id = shift;
2314         my $ldap_handle = &main::get_ldap_handle;
2315         my $result="/tmp/gosa_si_tmp_sources_list";
2317         # Remove old file
2318         if(stat($result)) {
2319                 unlink($result);
2320                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2321         }
2323         my $fh;
2324         open($fh, ">$result");
2325         if (not defined $fh) {
2326                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2327                 return undef;
2328         }
2329         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2330                 my $mesg=$ldap_handle->search(
2331                         base    => $main::ldap_server_dn,
2332                         scope   => 'base',
2333                         attrs   => 'FAIrepository',
2334                         filter  => 'objectClass=FAIrepositoryServer'
2335                 );
2336                 if($mesg->count) {
2337                         foreach my $entry(@{$mesg->{'entries'}}) {
2338                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2339                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2340                                         my $line = "deb $server $release";
2341                                         $sections =~ s/,/ /g;
2342                                         $line.= " $sections";
2343                                         print $fh $line."\n";
2344                                 }
2345                         }
2346                 }
2347         } else {
2348                 if (defined $main::ldap_server_dn){
2349                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2350                 } else {
2351                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2352                 }
2353         }
2354         close($fh);
2356         return $result;
2360 sub run_create_packages_list_db {
2361     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2362         my $session_id = $session->ID;
2364         my $task = POE::Wheel::Run->new(
2365                                         Priority => +20,
2366                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2367                                         StdoutEvent  => "session_run_result",
2368                                         StderrEvent  => "session_run_debug",
2369                                         CloseEvent   => "session_run_done",
2370                                         );
2371         $heap->{task}->{ $task->ID } = $task;
2375 sub create_packages_list_db {
2376         my ($ldap_handle, $sources_file, $session_id) = @_;
2377         
2378         # it should not be possible to trigger a recreation of packages_list_db
2379         # while packages_list_db is under construction, so set flag packages_list_under_construction
2380         # which is tested befor recreation can be started
2381         if (-r $packages_list_under_construction) {
2382                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2383                 return;
2384         } else {
2385                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2386                 # set packages_list_under_construction to true
2387                 system("touch $packages_list_under_construction");
2388                 @packages_list_statements=();
2389         }
2391         if (not defined $session_id) { $session_id = 0; }
2392         if (not defined $ldap_handle) { 
2393                 $ldap_handle= &get_ldap_handle();
2395                 if (not defined $ldap_handle) {
2396                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2397                         unlink($packages_list_under_construction);
2398                         return;
2399                 }
2400         }
2401         if (not defined $sources_file) { 
2402                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2403                 $sources_file = &create_sources_list($session_id);
2404         }
2406         if (not defined $sources_file) {
2407                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2408                 unlink($packages_list_under_construction);
2409                 return;
2410         }
2412         my $line;
2414         open(CONFIG, "<$sources_file") or do {
2415                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2416                 unlink($packages_list_under_construction);
2417                 return;
2418         };
2420         # Read lines
2421         while ($line = <CONFIG>){
2422                 # Unify
2423                 chop($line);
2424                 $line =~ s/^\s+//;
2425                 $line =~ s/^\s+/ /;
2427                 # Strip comments
2428                 $line =~ s/#.*$//g;
2430                 # Skip empty lines
2431                 if ($line =~ /^\s*$/){
2432                         next;
2433                 }
2435                 # Interpret deb line
2436                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2437                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2438                         my $section;
2439                         foreach $section (split(' ', $sections)){
2440                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2441                         }
2442                 }
2443         }
2445         close (CONFIG);
2447         find(\&cleanup_and_extract, keys( %repo_dirs ));
2448         &main::strip_packages_list_statements();
2449         unshift @packages_list_statements, "VACUUM";
2450         $packages_list_db->exec_statementlist(\@packages_list_statements);
2451         unlink($packages_list_under_construction);
2452         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2453         return;
2456 # This function should do some intensive task to minimize the db-traffic
2457 sub strip_packages_list_statements {
2458     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2459         my @new_statement_list=();
2460         my $hash;
2461         my $insert_hash;
2462         my $update_hash;
2463         my $delete_hash;
2464         my $local_timestamp=get_time();
2466         foreach my $existing_entry (@existing_entries) {
2467                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2468         }
2470         foreach my $statement (@packages_list_statements) {
2471                 if($statement =~ /^INSERT/i) {
2472                         # Assign the values from the insert statement
2473                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2474                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2475                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2476                                 # If section or description has changed, update the DB
2477                                 if( 
2478                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2479                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2480                                 ) {
2481                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2482                                 }
2483                         } else {
2484                                 # Insert a non-existing entry to db
2485                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2486                         }
2487                 } elsif ($statement =~ /^UPDATE/i) {
2488                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2489                         /^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;
2490                         foreach my $distribution (keys %{$hash}) {
2491                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2492                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2493                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2494                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2495                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2496                                                 my $section;
2497                                                 my $description;
2498                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2499                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2500                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2501                                                 }
2502                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2503                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2504                                                 }
2505                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2506                                         }
2507                                 }
2508                         }
2509                 }
2510         }
2512         # TODO: Check for orphaned entries
2514         # unroll the insert_hash
2515         foreach my $distribution (keys %{$insert_hash}) {
2516                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2517                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2518                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2519                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2520                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2521                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2522                                 ."'$local_timestamp')";
2523                         }
2524                 }
2525         }
2527         # unroll the update hash
2528         foreach my $distribution (keys %{$update_hash}) {
2529                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2530                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2531                                 my $set = "";
2532                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2533                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2534                                 }
2535                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2536                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2537                                 }
2538                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2539                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2540                                 }
2541                                 if(defined($set) and length($set) > 0) {
2542                                         $set .= "timestamp = '$local_timestamp'";
2543                                 } else {
2544                                         next;
2545                                 }
2546                                 push @new_statement_list, 
2547                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2548                                         ." distribution = '$distribution'"
2549                                         ." AND package = '$package'"
2550                                         ." AND version = '$version'";
2551                         }
2552                 }
2553         }
2555         @packages_list_statements = @new_statement_list;
2559 sub parse_package_info {
2560     my ($baseurl, $dist, $section, $session_id)= @_;
2561     my ($package);
2562     if (not defined $session_id) { $session_id = 0; }
2563     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2564     $repo_dirs{ "${repo_path}/pool" } = 1;
2566     foreach $package ("Packages.gz"){
2567         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2568         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2569         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2570     }
2571     
2575 sub get_package {
2576     my ($url, $dest, $session_id)= @_;
2577     if (not defined $session_id) { $session_id = 0; }
2579     my $tpath = dirname($dest);
2580     -d "$tpath" || mkpath "$tpath";
2582     # This is ugly, but I've no time to take a look at "how it works in perl"
2583     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2584         system("gunzip -cd '$dest' > '$dest.in'");
2585         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2586         unlink($dest);
2587         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2588     } else {
2589         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2590     }
2591     return 0;
2595 sub parse_package {
2596     my ($path, $dist, $srv_path, $session_id)= @_;
2597     if (not defined $session_id) { $session_id = 0;}
2598     my ($package, $version, $section, $description);
2599     my $PACKAGES;
2600     my $timestamp = &get_time();
2602     if(not stat("$path.in")) {
2603         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2604         return;
2605     }
2607     open($PACKAGES, "<$path.in");
2608     if(not defined($PACKAGES)) {
2609         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2610         return;
2611     }
2613     # Read lines
2614     while (<$PACKAGES>){
2615         my $line = $_;
2616         # Unify
2617         chop($line);
2619         # Use empty lines as a trigger
2620         if ($line =~ /^\s*$/){
2621             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2622             push(@packages_list_statements, $sql);
2623             $package = "none";
2624             $version = "none";
2625             $section = "none";
2626             $description = "none"; 
2627             next;
2628         }
2630         # Trigger for package name
2631         if ($line =~ /^Package:\s/){
2632             ($package)= ($line =~ /^Package: (.*)$/);
2633             next;
2634         }
2636         # Trigger for version
2637         if ($line =~ /^Version:\s/){
2638             ($version)= ($line =~ /^Version: (.*)$/);
2639             next;
2640         }
2642         # Trigger for description
2643         if ($line =~ /^Description:\s/){
2644             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2645             next;
2646         }
2648         # Trigger for section
2649         if ($line =~ /^Section:\s/){
2650             ($section)= ($line =~ /^Section: (.*)$/);
2651             next;
2652         }
2654         # Trigger for filename
2655         if ($line =~ /^Filename:\s/){
2656             my ($filename) = ($line =~ /^Filename: (.*)$/);
2657             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2658             next;
2659         }
2660     }
2662     close( $PACKAGES );
2663     unlink( "$path.in" );
2664     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2668 sub store_fileinfo {
2669     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2671     my %fileinfo = (
2672         'package' => $package,
2673         'dist' => $dist,
2674         'version' => $vers,
2675     );
2677     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2681 sub cleanup_and_extract {
2682     my $fileinfo = $repo_files{ $File::Find::name };
2684     if( defined $fileinfo ) {
2686         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2687         my $sql;
2688         my $package = $fileinfo->{ 'package' };
2689         my $newver = $fileinfo->{ 'version' };
2691         mkpath($dir);
2692         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2694                 if( -f "$dir/DEBIAN/templates" ) {
2696                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2698                         my $tmpl= "";
2699                         {
2700                                 local $/=undef;
2701                                 open FILE, "$dir/DEBIAN/templates";
2702                                 $tmpl = &encode_base64(<FILE>);
2703                                 close FILE;
2704                         }
2705                         rmtree("$dir/DEBIAN/templates");
2707                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2708                 push @packages_list_statements, $sql;
2709                 }
2710     }
2712     return;
2716 sub register_at_foreign_servers {   
2717     my ($kernel) = $_[KERNEL];
2719     # hole alle bekannten server aus known_server_db
2720     my $server_sql = "SELECT * FROM $known_server_tn";
2721     my $server_res = $known_server_db->exec_statement($server_sql);
2723     # no entries in known_server_db
2724     if (not ref(@$server_res[0]) eq "ARRAY") { 
2725         # TODO
2726     }
2728     # detect already connected clients
2729     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2730     my $client_res = $known_clients_db->exec_statement($client_sql);
2732     # send my server details to all other gosa-si-server within the network
2733     foreach my $hit (@$server_res) {
2734         my $hostname = @$hit[0];
2735         my $hostkey = &create_passwd;
2737         # add already connected clients to registration message 
2738         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2739         &add_content2xml_hash($myhash, 'key', $hostkey);
2740         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2741         
2742         # build registration message and send it
2743         my $foreign_server_msg = &create_xml_string($myhash);
2744         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2745     }
2746     
2747     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2748     return;
2752 #==== MAIN = main ==============================================================
2753 #  parse commandline options
2754 Getopt::Long::Configure( "bundling" );
2755 GetOptions("h|help" => \&usage,
2756         "c|config=s" => \$cfg_file,
2757         "f|foreground" => \$foreground,
2758         "v|verbose+" => \$verbose,
2759         "no-bus+" => \$no_bus,
2760         "no-arp+" => \$no_arp,
2761            );
2763 #  read and set config parameters
2764 &check_cmdline_param ;
2765 &read_configfile;
2766 &check_pid;
2768 $SIG{CHLD} = 'IGNORE';
2770 # forward error messages to logfile
2771 if( ! $foreground ) {
2772   open( STDIN,  '+>/dev/null' );
2773   open( STDOUT, '+>&STDIN'    );
2774   open( STDERR, '+>&STDIN'    );
2777 # Just fork, if we are not in foreground mode
2778 if( ! $foreground ) { 
2779     chdir '/'                 or die "Can't chdir to /: $!";
2780     $pid = fork;
2781     setsid                    or die "Can't start a new session: $!";
2782     umask 0;
2783 } else { 
2784     $pid = $$; 
2787 # Do something useful - put our PID into the pid_file
2788 if( 0 != $pid ) {
2789     open( LOCK_FILE, ">$pid_file" );
2790     print LOCK_FILE "$pid\n";
2791     close( LOCK_FILE );
2792     if( !$foreground ) { 
2793         exit( 0 ) 
2794     };
2797 # parse head url and revision from svn
2798 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2799 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2800 $server_headURL = defined $1 ? $1 : 'unknown' ;
2801 $server_revision = defined $2 ? $2 : 'unknown' ;
2802 if ($server_headURL =~ /\/tag\// || 
2803         $server_headURL =~ /\/branches\// ) {
2804     $server_status = "stable"; 
2805 } else {
2806     $server_status = "developmental" ;
2810 daemon_log(" ", 1);
2811 daemon_log("$0 started!", 1);
2812 daemon_log("status: $server_status", 1);
2813 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2815 if ($no_bus > 0) {
2816     $bus_activ = "false"
2819 # connect to incoming_db
2820 unlink($incoming_file_name);
2821 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2822 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2824 # connect to gosa-si job queue
2825 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2826 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2828 # connect to known_clients_db
2829 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2830 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2832 # connect to foreign_clients_db
2833 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2834 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2836 # connect to known_server_db
2837 unlink($known_server_file_name);
2838 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2839 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2841 # connect to login_usr_db
2842 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2843 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2845 # connect to fai_server_db and fai_release_db
2846 unlink($fai_server_file_name);
2847 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2848 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2850 unlink($fai_release_file_name);
2851 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2852 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2854 # connect to packages_list_db
2855 #unlink($packages_list_file_name);
2856 unlink($packages_list_under_construction);
2857 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2858 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2860 # connect to messaging_db
2861 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2862 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2865 # create xml object used for en/decrypting
2866 $xml = new XML::Simple();
2869 # foreign servers 
2870 my @foreign_server_list;
2872 # add foreign server from cfg file
2873 if ($foreign_server_string ne "") {
2874     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2875     foreach my $foreign_server (@cfg_foreign_server_list) {
2876         push(@foreign_server_list, $foreign_server);
2877     }
2880 # add foreign server from dns
2881 my @tmp_servers;
2882 if ( !$server_domain) {
2883     # Try our DNS Searchlist
2884     for my $domain(get_dns_domains()) {
2885         chomp($domain);
2886         my @tmp_domains= &get_server_addresses($domain);
2887         if(@tmp_domains) {
2888             for my $tmp_server(@tmp_domains) {
2889                 push @tmp_servers, $tmp_server;
2890             }
2891         }
2892     }
2893     if(@tmp_servers && length(@tmp_servers)==0) {
2894         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2895     }
2896 } else {
2897     @tmp_servers = &get_server_addresses($server_domain);
2898     if( 0 == @tmp_servers ) {
2899         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2900     }
2902 foreach my $server (@tmp_servers) { 
2903     unshift(@foreign_server_list, $server); 
2905 # eliminate duplicate entries
2906 @foreign_server_list = &del_doubles(@foreign_server_list);
2907 my $all_foreign_server = join(", ", @foreign_server_list);
2908 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2910 # add all found foreign servers to known_server
2911 my $act_timestamp = &get_time();
2912 foreach my $foreign_server (@foreign_server_list) {
2913     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2914             primkey=>['hostname'],
2915             hostname=>$foreign_server,
2916             status=>'not_jet_registered',
2917             hostkey=>"none",
2918             timestamp=>$act_timestamp,
2919             } );
2923 POE::Component::Server::TCP->new(
2924     Alias => "TCP_SERVER",
2925         Port => $server_port,
2926         ClientInput => sub {
2927         my ($kernel, $input) = @_[KERNEL, ARG0];
2928         push(@tasks, $input);
2929         push(@msgs_to_decrypt, $input);
2930         $kernel->yield("msg_to_decrypt");
2931         },
2932     InlineStates => {
2933         msg_to_decrypt => \&msg_to_decrypt,
2934         next_task => \&next_task,
2935         task_result => \&handle_task_result,
2936         task_done   => \&handle_task_done,
2937         task_debug  => \&handle_task_debug,
2938         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2939     }
2940 );
2942 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2944 # create session for repeatedly checking the job queue for jobs
2945 POE::Session->create(
2946         inline_states => {
2947                 _start => \&session_start,
2948         register_at_foreign_servers => \&register_at_foreign_servers,
2949         sig_handler => \&sig_handler,
2950         next_task => \&next_task,
2951         task_result => \&handle_task_result,
2952         task_done   => \&handle_task_done,
2953         task_debug  => \&handle_task_debug,
2954         watch_for_next_tasks => \&watch_for_next_tasks,
2955         watch_for_new_messages => \&watch_for_new_messages,
2956         watch_for_delivery_messages => \&watch_for_delivery_messages,
2957         watch_for_done_messages => \&watch_for_done_messages,
2958                 watch_for_new_jobs => \&watch_for_new_jobs,
2959         watch_for_done_jobs => \&watch_for_done_jobs,
2960         watch_for_old_known_clients => \&watch_for_old_known_clients,
2961         create_packages_list_db => \&run_create_packages_list_db,
2962         create_fai_server_db => \&run_create_fai_server_db,
2963         create_fai_release_db => \&run_create_fai_release_db,
2964         session_run_result => \&session_run_result,
2965         session_run_debug => \&session_run_debug,
2966         session_run_done => \&session_run_done,
2967         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2968         }
2969 );
2972 # import all modules
2973 &import_modules;
2975 # TODO
2976 # check wether all modules are gosa-si valid passwd check
2980 POE::Kernel->run();
2981 exit;