Code

bugfix: gosa-si-server: if incoming target_address is an ip address, detect ip addres...
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # 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         }
325         chomp($msg);
326         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
327         if($level <= $verbose){
328             my ($seconds, $minutes, $hours, $monthday, $month,
329                     $year, $weekday, $yearday, $sommertime) = localtime(time);
330             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
331             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
332             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
333             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
334             $month = $monthnames[$month];
335             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
336             $year+=1900;
337             my $name = $prg;
339             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
340             print LOG_HANDLE $log_msg;
341             if( $foreground ) { 
342                 print STDERR $log_msg;
343             }
344         }
345         close( LOG_HANDLE );
346     }
350 #===  FUNCTION  ================================================================
351 #         NAME:  check_cmdline_param
352 #   PARAMETERS:  nothing
353 #      RETURNS:  nothing
354 #  DESCRIPTION:  validates commandline parameter
355 #===============================================================================
356 sub check_cmdline_param () {
357     my $err_config;
358     my $err_counter = 0;
359         if(not defined($cfg_file)) {
360                 $cfg_file = "/etc/gosa-si/server.conf";
361                 if(! -r $cfg_file) {
362                         $err_config = "please specify a config file";
363                         $err_counter += 1;
364                 }
365     }
366     if( $err_counter > 0 ) {
367         &usage( "", 1 );
368         if( defined( $err_config)) { print STDERR "$err_config\n"}
369         print STDERR "\n";
370         exit( -1 );
371     }
375 #===  FUNCTION  ================================================================
376 #         NAME:  check_pid
377 #   PARAMETERS:  nothing
378 #      RETURNS:  nothing
379 #  DESCRIPTION:  handels pid processing
380 #===============================================================================
381 sub check_pid {
382     $pid = -1;
383     # Check, if we are already running
384     if( open(LOCK_FILE, "<$pid_file") ) {
385         $pid = <LOCK_FILE>;
386         if( defined $pid ) {
387             chomp( $pid );
388             if( -f "/proc/$pid/stat" ) {
389                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
390                 if( $stat ) {
391                                         daemon_log("ERROR: Already running",1);
392                     close( LOCK_FILE );
393                     exit -1;
394                 }
395             }
396         }
397         close( LOCK_FILE );
398         unlink( $pid_file );
399     }
401     # create a syslog msg if it is not to possible to open PID file
402     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
403         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
404         if (open(LOCK_FILE, '<', $pid_file)
405                 && ($pid = <LOCK_FILE>))
406         {
407             chomp($pid);
408             $msg .= "(PID $pid)\n";
409         } else {
410             $msg .= "(unable to read PID)\n";
411         }
412         if( ! ($foreground) ) {
413             openlog( $0, "cons,pid", "daemon" );
414             syslog( "warning", $msg );
415             closelog();
416         }
417         else {
418             print( STDERR " $msg " );
419         }
420         exit( -1 );
421     }
424 #===  FUNCTION  ================================================================
425 #         NAME:  import_modules
426 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
427 #                are stored
428 #      RETURNS:  nothing
429 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
430 #                state is on is imported by "require 'file';"
431 #===============================================================================
432 sub import_modules {
433     daemon_log(" ", 1);
435     if (not -e $modules_path) {
436         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
437     }
439     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
440     while (defined (my $file = readdir (DIR))) {
441         if (not $file =~ /(\S*?).pm$/) {
442             next;
443         }
444                 my $mod_name = $1;
446         if( $file =~ /ArpHandler.pm/ ) {
447             if( $no_arp > 0 ) {
448                 next;
449             }
450         }
451         
452         eval { require $file; };
453         if ($@) {
454             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
455             daemon_log("$@", 5);
456                 } else {
457                         my $info = eval($mod_name.'::get_module_info()');
458                         # Only load module if get_module_info() returns a non-null object
459                         if( $info ) {
460                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
461                                 $known_modules->{$mod_name} = $info;
462                                 daemon_log("0 INFO: module $mod_name loaded", 5);
463                         }
464                 }
465     }   
466     close (DIR);
470 #===  FUNCTION  ================================================================
471 #         NAME:  sig_int_handler
472 #   PARAMETERS:  signal - string - signal arose from system
473 #      RETURNS:  noting
474 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
475 #===============================================================================
476 sub sig_int_handler {
477     my ($signal) = @_;
479 #       if (defined($ldap_handle)) {
480 #               $ldap_handle->disconnect;
481 #       }
482     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
483     
485     daemon_log("shutting down gosa-si-server", 1);
486     system("kill `ps -C gosa-si-server -o pid=`");
488 $SIG{INT} = \&sig_int_handler;
491 sub check_key_and_xml_validity {
492     my ($crypted_msg, $module_key, $session_id) = @_;
493     my $msg;
494     my $msg_hash;
495     my $error_string;
496     eval{
497         $msg = &decrypt_msg($crypted_msg, $module_key);
499         if ($msg =~ /<xml>/i){
500             $msg =~ s/\s+/ /g;  # just for better daemon_log
501             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
502             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
504             ##############
505             # check header
506             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
507             my $header_l = $msg_hash->{'header'};
508             if( 1 > @{$header_l} ) { die 'empty header tag'; }
509             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
510             my $header = @{$header_l}[0];
511             if( 0 == length $header) { die 'empty string in header tag'; }
513             ##############
514             # check source
515             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
516             my $source_l = $msg_hash->{'source'};
517             if( 1 > @{$source_l} ) { die 'empty source tag'; }
518             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
519             my $source = @{$source_l}[0];
520             if( 0 == length $source) { die 'source error'; }
522             ##############
523             # check target
524             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
525             my $target_l = $msg_hash->{'target'};
526             if( 1 > @{$target_l} ) { die 'empty target tag'; }
527         }
528     };
529     if($@) {
530         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
531         $msg = undef;
532         $msg_hash = undef;
533     }
535     return ($msg, $msg_hash);
539 sub check_outgoing_xml_validity {
540     my ($msg) = @_;
542     my $msg_hash;
543     eval{
544         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
546         ##############
547         # check header
548         my $header_l = $msg_hash->{'header'};
549         if( 1 != @{$header_l} ) {
550             die 'no or more than one headers specified';
551         }
552         my $header = @{$header_l}[0];
553         if( 0 == length $header) {
554             die 'header has length 0';
555         }
557         ##############
558         # check source
559         my $source_l = $msg_hash->{'source'};
560         if( 1 != @{$source_l} ) {
561             die 'no or more than 1 sources specified';
562         }
563         my $source = @{$source_l}[0];
564         if( 0 == length $source) {
565             die 'source has length 0';
566         }
567         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
568                 $source =~ /^GOSA$/i ) {
569             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
570         }
571         
572         ##############
573         # check target  
574         my $target_l = $msg_hash->{'target'};
575         if( 0 == @{$target_l} ) {
576             die "no targets specified";
577         }
578         foreach my $target (@$target_l) {
579             if( 0 == length $target) {
580                 die "target has length 0";
581             }
582             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
583                     $target =~ /^GOSA$/i ||
584                     $target =~ /^\*$/ ||
585                     $target =~ /KNOWN_SERVER/i ||
586                     $target =~ /JOBDB/i ||
587                     $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 ){
588                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
589             }
590         }
591     };
592     if($@) {
593         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
594         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
595         $msg_hash = undef;
596     }
598     return ($msg_hash);
602 sub input_from_known_server {
603     my ($input, $remote_ip, $session_id) = @_ ;  
604     my ($msg, $msg_hash, $module);
606     my $sql_statement= "SELECT * FROM known_server";
607     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
609     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
610         my $host_name = $hit->{hostname};
611         if( not $host_name =~ "^$remote_ip") {
612             next;
613         }
614         my $host_key = $hit->{hostkey};
615         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
616         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
618         # check if module can open msg envelope with module key
619         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
620         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
621             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
622             daemon_log("$@", 8);
623             next;
624         }
625         else {
626             $msg = $tmp_msg;
627             $msg_hash = $tmp_msg_hash;
628             $module = "ServerPackages";
629             last;
630         }
631     }
633     if( (!$msg) || (!$msg_hash) || (!$module) ) {
634         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
635     }
636   
637     return ($msg, $msg_hash, $module);
641 sub input_from_known_client {
642     my ($input, $remote_ip, $session_id) = @_ ;  
643     my ($msg, $msg_hash, $module);
645     my $sql_statement= "SELECT * FROM known_clients";
646     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
647     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
648         my $host_name = $hit->{hostname};
649         if( not $host_name =~ /^$remote_ip:\d*$/) {
650                 next;
651                 }
652         my $host_key = $hit->{hostkey};
653         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
654         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
656         # check if module can open msg envelope with module key
657         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
659         if( (!$msg) || (!$msg_hash) ) {
660             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
661             &daemon_log("$@", 8);
662             next;
663         }
664         else {
665             $module = "ClientPackages";
666             last;
667         }
668     }
670     if( (!$msg) || (!$msg_hash) || (!$module) ) {
671         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
672     }
674     return ($msg, $msg_hash, $module);
678 sub input_from_unknown_host {
679     no strict "refs";
680     my ($input, $session_id) = @_ ;
681     my ($msg, $msg_hash, $module);
682     my $error_string;
683     
684         my %act_modules = %$known_modules;
685         
686     while( my ($mod, $info) = each(%act_modules)) {
688         # check a key exists for this module
689         my $module_key = ${$mod."_key"};
690         if( not defined $module_key ) {
691             if( $mod eq 'ArpHandler' ) {
692                 next;
693             }
694             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
695             next;
696         }
697         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
699         # check if module can open msg envelope with module key
700         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
701         if( (not defined $msg) || (not defined $msg_hash) ) {
702             next;
703         }
704         else {
705             $module = $mod;
706             last;
707         }
708     }
710     if( (!$msg) || (!$msg_hash) || (!$module)) {
711         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
712     }
714     return ($msg, $msg_hash, $module);
718 sub create_ciphering {
719     my ($passwd) = @_;
720         if((!defined($passwd)) || length($passwd)==0) {
721                 $passwd = "";
722         }
723     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
724     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
725     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
726     $my_cipher->set_iv($iv);
727     return $my_cipher;
731 sub encrypt_msg {
732     my ($msg, $key) = @_;
733     my $my_cipher = &create_ciphering($key);
734     my $len;
735     {
736             use bytes;
737             $len= 16-length($msg)%16;
738     }
739     $msg = "\0"x($len).$msg;
740     $msg = $my_cipher->encrypt($msg);
741     chomp($msg = &encode_base64($msg));
742     # there are no newlines allowed inside msg
743     $msg=~ s/\n//g;
744     return $msg;
748 sub decrypt_msg {
750     my ($msg, $key) = @_ ;
751     $msg = &decode_base64($msg);
752     my $my_cipher = &create_ciphering($key);
753     $msg = $my_cipher->decrypt($msg); 
754     $msg =~ s/\0*//g;
755     return $msg;
759 sub get_encrypt_key {
760     my ($target) = @_ ;
761     my $encrypt_key;
762     my $error = 0;
764     # target can be in known_server
765     if( not defined $encrypt_key ) {
766         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
767         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
768         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
769             my $host_name = $hit->{hostname};
770             if( $host_name ne $target ) {
771                 next;
772             }
773             $encrypt_key = $hit->{hostkey};
774             last;
775         }
776     }
778     # target can be in known_client
779     if( not defined $encrypt_key ) {
780         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
781         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
782         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
783             my $host_name = $hit->{hostname};
784             if( $host_name ne $target ) {
785                 next;
786             }
787             $encrypt_key = $hit->{hostkey};
788             last;
789         }
790     }
792     return $encrypt_key;
796 #===  FUNCTION  ================================================================
797 #         NAME:  open_socket
798 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
799 #                [PeerPort] string necessary if port not appended by PeerAddr
800 #      RETURNS:  socket IO::Socket::INET
801 #  DESCRIPTION:  open a socket to PeerAddr
802 #===============================================================================
803 sub open_socket {
804     my ($PeerAddr, $PeerPort) = @_ ;
805     if(defined($PeerPort)){
806         $PeerAddr = $PeerAddr.":".$PeerPort;
807     }
808     my $socket;
809     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
810             Porto => "tcp",
811             Type => SOCK_STREAM,
812             Timeout => 5,
813             );
814     if(not defined $socket) {
815         return;
816     }
817 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
818     return $socket;
822 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
823 #===  FUNCTION  ================================================================
824 #         NAME:  get_ip 
825 #   PARAMETERS:  interface name (i.e. eth0)
826 #      RETURNS:  (ip address) 
827 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
828 #===============================================================================
829 #sub get_ip {
830 #       my $ifreq= shift;
831 #       my $result= "";
832 #       my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
833 #       my $proto= getprotobyname('ip');
835 #       socket SOCKET, PF_INET, SOCK_DGRAM, $proto
836 #               or die "socket: $!";
838 #       if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
839 #               my ($if, $sin)    = unpack 'a16 a16', $ifreq;
840 #               my ($port, $addr) = sockaddr_in $sin;
841 #               my $ip            = inet_ntoa $addr;
843 #               if ($ip && length($ip) > 0) {
844 #                       $result = $ip;
845 #               }
846 #       }
848 #       return $result;
849 #}
852 sub get_local_ip_for_remote_ip {
853         my $remote_ip= shift;
854         my $result="0.0.0.0";
856         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
857                 if($remote_ip eq "127.0.0.1") {
858                         $result = "127.0.0.1";
859                 } else {
860                         my $PROC_NET_ROUTE= ('/proc/net/route');
862                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
863                                 or die "Could not open $PROC_NET_ROUTE";
865                         my @ifs = <PROC_NET_ROUTE>;
867                         close(PROC_NET_ROUTE);
869                         # Eat header line
870                         shift @ifs;
871                         chomp @ifs;
872                         foreach my $line(@ifs) {
873                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
874                                 my $destination;
875                                 my $mask;
876                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
877                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
879                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
880                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
881                                         # destination matches route, save mac and exit
882                                         $result= &get_ip($Iface);
883                                         last;
884                                 }
885                         }
886                 }
887         } else {
888                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
889         }
890         return $result;
894 sub send_msg_to_target {
895     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
896     my $error = 0;
897     my $header;
898     my $timestamp = &get_time();
899     my $new_status;
900     my $act_status;
901     my ($sql_statement, $res);
902   
903     if( $msg_header ) {
904         $header = "'$msg_header'-";
905     } else {
906         $header = "";
907     }
909         # Patch the source ip
910         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
911                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
912                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
913         }
915     # encrypt xml msg
916     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
918     # opensocket
919     my $socket = &open_socket($address);
920     if( !$socket ) {
921         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
922         $error++;
923     }
924     
925     if( $error == 0 ) {
926         # send xml msg
927         print $socket $crypted_msg."\n";
929         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
930         daemon_log("$session_id DEBUG: message:\n$msg", 9);
931         
932     }
934     # close socket in any case
935     if( $socket ) {
936         close $socket;
937     }
939     if( $error > 0 ) { $new_status = "down"; }
940     else { $new_status = $msg_header; }
943     # known_clients
944     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
945     $res = $known_clients_db->select_dbentry($sql_statement);
946     if( keys(%$res) == 1) {
947         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
948         if ($act_status eq "down" && $new_status eq "down") {
949             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
950             $res = $known_clients_db->del_dbentry($sql_statement);
951             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
952         } else { 
953             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
954             $res = $known_clients_db->update_dbentry($sql_statement);
955             if($new_status eq "down"){
956                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957             } else {
958                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
959             }
960         }
961     }
963     # known_server
964     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
965     $res = $known_server_db->select_dbentry($sql_statement);
966     if( keys(%$res) == 1) {
967         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
968         if ($act_status eq "down" && $new_status eq "down") {
969             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
970             $res = $known_server_db->del_dbentry($sql_statement);
971             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
972         } 
973         else { 
974             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
975             $res = $known_server_db->update_dbentry($sql_statement);
976             if($new_status eq "down"){
977                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
978             } else {
979                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
980             }
981         }
982     }
983     return $error; 
987 sub update_jobdb_status_for_send_msgs {
988     my ($answer, $error) = @_;
989     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
990         my $jobdb_id = $1;
991             
992         # sending msg faild
993         if( $error ) {
994             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
995                 my $sql_statement = "UPDATE $job_queue_tn ".
996                     "SET status='error', result='can not deliver msg, please consult log file' ".
997                     "WHERE id=$jobdb_id";
998                 my $res = $job_db->update_dbentry($sql_statement);
999             }
1001         # sending msg was successful
1002         } else {
1003             my $sql_statement = "UPDATE $job_queue_tn ".
1004                 "SET status='done' ".
1005                 "WHERE id=$jobdb_id AND status='processed'";
1006             my $res = $job_db->update_dbentry($sql_statement);
1007         }
1008     }
1012 sub sig_handler {
1013         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1014         daemon_log("0 INFO got signal '$signal'", 1); 
1015         $kernel->sig_handled();
1016         return;
1020 sub msg_to_decrypt {
1021     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1022     my $session_id = $session->ID;
1023     my ($msg, $msg_hash, $module);
1024     my $error = 0;
1026     # hole neue msg aus @msgs_to_decrypt
1027     my $next_msg = shift @msgs_to_decrypt;
1028     
1029     # entschlüssle sie
1031     # msg is from a new client or gosa
1032     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1033     # msg is from a gosa-si-server or gosa-si-bus
1034     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1035         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1036     }
1037     # msg is from a gosa-si-client
1038     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1039         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1040     }
1041     # an error occurred
1042     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1043         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1044         # could not understand a msg from its server the client cause a re-registering process
1045         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1046                 "' to cause a re-registering of the client if necessary", 5);
1047         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1048         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1049         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1050             my $host_name = $hit->{'hostname'};
1051             my $host_key = $hit->{'hostkey'};
1052             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1053             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1054             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1055         }
1056         $error++;
1057     }
1060     my $header;
1061     my $target;
1062     my $source;
1063     my $done = 0;
1064     my $sql;
1065     my $res;
1066     # check whether this message should be processed here
1067     if ($error == 0) {
1068         $header = @{$msg_hash->{'header'}}[0];
1069         $target = @{$msg_hash->{'target'}}[0];
1070         $source = @{$msg_hash->{'source'}}[0];
1071         my ($target_ip, $target_port) = split(':', $target);
1072                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1073                         my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1074                 }
1076         # target and source is equal to GOSA -> process here
1077         if (not $done) {
1078             if ($target eq "GOSA" && $source eq "GOSA") {
1079                 $done = 1;                    
1080             }
1081         }
1083         # target is own address without forward_to_gosa-tag -> process here
1084         if (not $done) {
1085             if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1086                 $done = 1;
1087                 if ($source eq "GOSA") {
1088                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1089                 }
1090                 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1091             }
1092         }
1094         # target is a client address in known_clients -> process here
1095         if (not $done) {
1096             $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1097             $res = $known_clients_db->select_dbentry($sql);
1098             if (keys(%$res) > 0) {
1099                 $done = 1; 
1100                 my $hostname = $res->{1}->{'hostname'};
1101                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1102                 print STDERR "target is a client address in known_clients -> process here\n";
1103             }
1104         }
1106         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1107         if (not $done) {
1108             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1109             my $gosa_at;
1110             my $gosa_session_id;
1111             if (($target eq $server_address) && (defined $forward_to_gosa)){
1112                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1113                 if ($gosa_at ne $server_address) {
1114                     $done = 1;
1115                     print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1116                 }
1117             }
1118         }
1120         # if message should be processed here -> add message to incoming_db
1121         if ($done) {
1123             # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1124             # so gosa-si-server knows how to process this kind of messages
1125             if ($header =~ /^gosa_/ || $header =~ /job_/) {
1126                 $module = "GosaPackages";
1127             }
1129             my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1130                     primkey=>[],
1131                     headertag=>$header,
1132                     targettag=>$target,
1133                     xmlmessage=>$msg,
1134                     timestamp=>&get_time,
1135                     module=>$module,
1136                     sessionid=>$session_id,
1137                     } );
1139         }
1141         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1142         if (not $done) {
1143             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1144             my $gosa_at;
1145             my $gosa_session_id;
1146             if (($target eq $server_address) && (defined $forward_to_gosa)){
1147                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1148                 if ($gosa_at eq $server_address) {
1149                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1150                     if( defined $session_reference ) {
1151                         $heap = $session_reference->get_heap();
1152                     }
1153                     if(exists $heap->{'client'}) {
1154                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1155                         $heap->{'client'}->put($msg);
1156                     }
1157                     $done = 1;
1158                     print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1159                 }
1160             }
1162         }
1164         # target is a client address in foreign_clients -> forward to registration server
1165         if (not $done) {
1166             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1167             $res = $foreign_clients_db->select_dbentry($sql);
1168             if (keys(%$res) > 0) {
1169                 my $hostname = $res->{1}->{'hostname'};
1170                 my $regserver = $res->{1}->{'regserver'};
1171                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1172                 my $res = $known_server_db->select_dbentry($sql);
1173                 if (keys(%$res) > 0) {
1174                     my $regserver_key = $res->{1}->{'hostkey'};
1175                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1176                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1177                     if ($source eq "GOSA") {
1178                         $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1179                     }
1180                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1181                 }
1182                 $done = 1;
1183                 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1184             }
1185         }
1187         # target is a server address -> forward to server
1188         if (not $done) {
1189             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1190             $res = $known_server_db->select_dbentry($sql);
1191             if (keys(%$res) > 0) {
1192                 my $hostkey = $res->{1}->{'hostkey'};
1194                 if ($source eq "GOSA") {
1195                     $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1196                     $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1198                 }
1200                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1201                 $done = 1;
1202                 print STDERR "target is a server address -> forward to server\n";
1203             }
1206         }
1208         if (not $done) {
1209             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1210         }
1211     }
1213     return;
1217 sub next_task {
1218     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1219     my $running_task = POE::Wheel::Run->new(
1220             Program => sub { process_task($session, $heap, $task) },
1221             StdioFilter => POE::Filter::Reference->new(),
1222             StdoutEvent  => "task_result",
1223             StderrEvent  => "task_debug",
1224             CloseEvent   => "task_done",
1225             );
1226     $heap->{task}->{ $running_task->ID } = $running_task;
1229 sub handle_task_result {
1230     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1231     my $client_answer = $result->{'answer'};
1232     if( $client_answer =~ s/session_id=(\d+)$// ) {
1233         my $session_id = $1;
1234         if( defined $session_id ) {
1235             my $session_reference = $kernel->ID_id_to_session($session_id);
1236             if( defined $session_reference ) {
1237                 $heap = $session_reference->get_heap();
1238             }
1239         }
1241         if(exists $heap->{'client'}) {
1242             $heap->{'client'}->put($client_answer);
1243         }
1244     }
1245     $kernel->sig(CHLD => "child_reap");
1248 sub handle_task_debug {
1249     my $result = $_[ARG0];
1250     print STDERR "$result\n";
1253 sub handle_task_done {
1254     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1255     delete $heap->{task}->{$task_id};
1258 sub process_task {
1259     no strict "refs";
1260     my ($session, $heap, $task) = @_;
1261     my $error = 0;
1262     my $answer_l;
1263     my ($answer_header, @answer_target_l, $answer_source);
1264     my $client_answer = "";
1266     # prepare all variables needed to process message
1267     my $msg = $task->{'xmlmessage'};
1268     my $incoming_id = $task->{'id'};
1269     my $module = $task->{'module'};
1270     my $header =  $task->{'headertag'};
1271     my $session_id = $task->{'sessionid'};
1272     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1273     my $source = @{$msg_hash->{'source'}}[0];
1274     
1275     # set timestamp of incoming client uptodate, so client will not 
1276     # be deleted from known_clients because of expiration
1277     my $act_time = &get_time();
1278     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1279     my $res = $known_clients_db->exec_statement($sql);
1281     ######################
1282     # process incoming msg
1283     if( $error == 0) {
1284         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1285         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1286         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1288         if ( 0 < @{$answer_l} ) {
1289             my $answer_str = join("\n", @{$answer_l});
1290             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1291                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1292             }
1293             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1294         } else {
1295             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1296         }
1298     }
1299     if( !$answer_l ) { $error++ };
1301     ########
1302     # answer
1303     if( $error == 0 ) {
1305         foreach my $answer ( @{$answer_l} ) {
1306             # check outgoing msg to xml validity
1307             my $answer_hash = &check_outgoing_xml_validity($answer);
1308             if( not defined $answer_hash ) { next; }
1309             
1310             $answer_header = @{$answer_hash->{'header'}}[0];
1311             @answer_target_l = @{$answer_hash->{'target'}};
1312             $answer_source = @{$answer_hash->{'source'}}[0];
1314             # deliver msg to all targets 
1315             foreach my $answer_target ( @answer_target_l ) {
1317                 # targets of msg are all gosa-si-clients in known_clients_db
1318                 if( $answer_target eq "*" ) {
1319                     # answer is for all clients
1320                     my $sql_statement= "SELECT * FROM known_clients";
1321                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1322                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1323                         my $host_name = $hit->{hostname};
1324                         my $host_key = $hit->{hostkey};
1325                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1326                         &update_jobdb_status_for_send_msgs($answer, $error);
1327                     }
1328                 }
1330                 # targets of msg are all gosa-si-server in known_server_db
1331                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1332                     # answer is for all server in known_server
1333                     my $sql_statement= "SELECT * FROM $known_server_tn";
1334                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1335                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1336                         my $host_name = $hit->{hostname};
1337                         my $host_key = $hit->{hostkey};
1338                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1339                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1340                         &update_jobdb_status_for_send_msgs($answer, $error);
1341                     }
1342                 }
1344                 # target of msg is GOsa
1345                                 elsif( $answer_target eq "GOSA" ) {
1346                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1347                                         my $add_on = "";
1348                     if( defined $session_id ) {
1349                         $add_on = ".session_id=$session_id";
1350                     }
1351                     # answer is for GOSA and has to returned to connected client
1352                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1353                     $client_answer = $gosa_answer.$add_on;
1354                 }
1356                 # target of msg is job queue at this host
1357                 elsif( $answer_target eq "JOBDB") {
1358                     $answer =~ /<header>(\S+)<\/header>/;   
1359                     my $header;
1360                     if( defined $1 ) { $header = $1; }
1361                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1362                     &update_jobdb_status_for_send_msgs($answer, $error);
1363                 }
1365                 # target of msg is a mac address
1366                 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 ) {
1367                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1368                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1369                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1370                     my $found_ip_flag = 0;
1371                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1372                         my $host_name = $hit->{hostname};
1373                         my $host_key = $hit->{hostkey};
1374                         $answer =~ s/$answer_target/$host_name/g;
1375                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1376                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1377                         &update_jobdb_status_for_send_msgs($answer, $error);
1378                         $found_ip_flag++ ;
1379                     }   
1380                     if( $found_ip_flag == 0) {
1381                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1382                         if( $bus_activ eq "true" ) { 
1383                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1384                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1385                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1386                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1387                                 my $bus_address = $hit->{hostname};
1388                                 my $bus_key = $hit->{hostkey};
1389                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1390                                 &update_jobdb_status_for_send_msgs($answer, $error);
1391                                 last;
1392                             }
1393                         }
1395                     }
1397                 #  answer is for one specific host   
1398                 } else {
1399                     # get encrypt_key
1400                     my $encrypt_key = &get_encrypt_key($answer_target);
1401                     if( not defined $encrypt_key ) {
1402                         # unknown target, forward msg to bus
1403                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1404                         if( $bus_activ eq "true" ) { 
1405                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1406                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1407                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1408                             my $res_length = keys( %{$query_res} );
1409                             if( $res_length == 0 ){
1410                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1411                                         "no bus found in known_server", 3);
1412                             }
1413                             else {
1414                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1415                                     my $bus_key = $hit->{hostkey};
1416                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1417                                     &update_jobdb_status_for_send_msgs($answer, $error);
1418                                 }
1419                             }
1420                         }
1421                         next;
1422                     }
1423                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1424                     &update_jobdb_status_for_send_msgs($answer, $error);
1425                 }
1426             }
1427         }
1428     }
1430     my $filter = POE::Filter::Reference->new();
1431     my %result = ( 
1432             status => "seems ok to me",
1433             answer => $client_answer,
1434             );
1436     my $output = $filter->put( [ \%result ] );
1437     print @$output;
1442 sub session_start {
1443     my ($kernel) = $_[KERNEL];
1444     &trigger_db_loop($kernel);
1445     $global_kernel = $kernel;
1446     $kernel->yield('register_at_foreign_servers');
1447         $kernel->yield('create_fai_server_db', $fai_server_tn );
1448         $kernel->yield('create_fai_release_db', $fai_release_tn );
1449     $kernel->yield('watch_for_next_tasks');
1450         $kernel->sig(USR1 => "sig_handler");
1451         $kernel->sig(USR2 => "create_packages_list_db");
1452         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1453         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1454         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1455     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1456         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1457     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1461 sub trigger_db_loop {
1462         my ($kernel) = @_ ;
1463 #       $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1464 #       $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1465 #       $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1466 #    $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1467 #       $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1468 #    $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1472 sub watch_for_done_jobs {
1473     my ($kernel,$heap) = @_[KERNEL, HEAP];
1475     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1476         my $res = $job_db->select_dbentry( $sql_statement );
1478     while( my ($id, $hit) = each %{$res} ) {
1479         my $jobdb_id = $hit->{id};
1480         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1481         my $res = $job_db->del_dbentry($sql_statement); 
1482     }
1484     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1488 sub watch_for_new_jobs {
1489         if($watch_for_new_jobs_in_progress == 0) {
1490                 $watch_for_new_jobs_in_progress = 1;
1491                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1493                 # check gosa job queue for jobs with executable timestamp
1494                 my $timestamp = &get_time();
1495                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1496                 my $res = $job_db->exec_statement( $sql_statement );
1498                 # Merge all new jobs that would do the same actions
1499                 my @drops;
1500                 my $hits;
1501                 foreach my $hit (reverse @{$res} ) {
1502                         my $macaddress= lc @{$hit}[8];
1503                         my $headertag= @{$hit}[5];
1504                         if(
1505                                 defined($hits->{$macaddress}) &&
1506                                 defined($hits->{$macaddress}->{$headertag}) &&
1507                                 defined($hits->{$macaddress}->{$headertag}[0])
1508                         ) {
1509                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1510                         }
1511                         $hits->{$macaddress}->{$headertag}= $hit;
1512                 }
1514                 # Delete new jobs with a matching job in state 'processing'
1515                 foreach my $macaddress (keys %{$hits}) {
1516                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1517                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1518                                 if(defined($jobdb_id)) {
1519                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1520                                         my $res = $job_db->exec_statement( $sql_statement );
1521                                         foreach my $hit (@{$res}) {
1522                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1523                                         }
1524                                 } else {
1525                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1526                                 }
1527                         }
1528                 }
1530                 # Commit deletion
1531                 $job_db->exec_statementlist(\@drops);
1533                 # Look for new jobs that could be executed
1534                 foreach my $macaddress (keys %{$hits}) {
1536                         # Look if there is an executing job
1537                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1538                         my $res = $job_db->exec_statement( $sql_statement );
1540                         # Skip new jobs for host if there is a processing job
1541                         if(defined($res) and defined @{$res}[0]) {
1542                                 next;
1543                         }
1545                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1546                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1547                                 if(defined($jobdb_id)) {
1548                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1550                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1551                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1552                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1554                                         # expect macaddress is unique!!!!!!
1555                                         my $target = $res_hash->{1}->{hostname};
1557                                         # change header
1558                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1560                                         # add sqlite_id
1561                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1563                                         $job_msg =~ /<header>(\S+)<\/header>/;
1564                                         my $header = $1 ;
1565                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1567                                         # update status in job queue to 'processing'
1568                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1569                                         my $res = $job_db->update_dbentry($sql_statement);
1570 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1572                                         # We don't want parallel processing
1573                                         last;
1574                                 }
1575                         }
1576                 }
1578                 $watch_for_new_jobs_in_progress = 0;
1579                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1580         }
1584 sub watch_for_new_messages {
1585     my ($kernel,$heap) = @_[KERNEL, HEAP];
1586     my @coll_user_msg;   # collection list of outgoing messages
1587     
1588     # check messaging_db for new incoming messages with executable timestamp
1589     my $timestamp = &get_time();
1590     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1591     my $res = $messaging_db->exec_statement( $sql_statement );
1592         foreach my $hit (@{$res}) {
1594         # create outgoing messages
1595         my $message_to = @{$hit}[3];
1596         # translate message_to to plain login name
1597         my @message_to_l = split(/,/, $message_to);  
1598                 my %receiver_h; 
1599                 foreach my $receiver (@message_to_l) {
1600                         if ($receiver =~ /^u_([\s\S]*)$/) {
1601                                 $receiver_h{$1} = 0;
1602                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1603                                 my $group_name = $1;
1604                                 # fetch all group members from ldap and add them to receiver hash
1605                                 my $ldap_handle = &get_ldap_handle();
1606                                 if (defined $ldap_handle) {
1607                                                 my $mesg = $ldap_handle->search(
1608                                                                                 base => $ldap_base,
1609                                                                                 scope => 'sub',
1610                                                                                 attrs => ['memberUid'],
1611                                                                                 filter => "cn=$group_name",
1612                                                                                 );
1613                                                 if ($mesg->count) {
1614                                                                 my @entries = $mesg->entries;
1615                                                                 foreach my $entry (@entries) {
1616                                                                                 my @receivers= $entry->get_value("memberUid");
1617                                                                                 foreach my $receiver (@receivers) { 
1618                                                                                                 $receiver_h{$1} = 0;
1619                                                                                 }
1620                                                                 }
1621                                                 } 
1622                                                 # translating errors ?
1623                                                 if ($mesg->code) {
1624                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1625                                                 }
1626                                 # ldap handle error ?           
1627                                 } else {
1628                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1629                                 }
1630                         } else {
1631                                 my $sbjct = &encode_base64(@{$hit}[1]);
1632                                 my $msg = &encode_base64(@{$hit}[7]);
1633                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1634                         }
1635                 }
1636                 my @receiver_l = keys(%receiver_h);
1638         my $message_id = @{$hit}[0];
1640         #add each outgoing msg to messaging_db
1641         my $receiver;
1642         foreach $receiver (@receiver_l) {
1643             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1644                 "VALUES ('".
1645                 $message_id."', '".    # id
1646                 @{$hit}[1]."', '".     # subject
1647                 @{$hit}[2]."', '".     # message_from
1648                 $receiver."', '".      # message_to
1649                 "none"."', '".         # flag
1650                 "out"."', '".          # direction
1651                 @{$hit}[6]."', '".     # delivery_time
1652                 @{$hit}[7]."', '".     # message
1653                 $timestamp."'".     # timestamp
1654                 ")";
1655             &daemon_log("M DEBUG: $sql_statement", 1);
1656             my $res = $messaging_db->exec_statement($sql_statement);
1657             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1658         }
1660         # set incoming message to flag d=deliverd
1661         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1662         &daemon_log("M DEBUG: $sql_statement", 7);
1663         $res = $messaging_db->update_dbentry($sql_statement);
1664         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1665     }
1667     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1668     return;
1671 sub watch_for_delivery_messages {
1672     my ($kernel, $heap) = @_[KERNEL, HEAP];
1674     # select outgoing messages
1675     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1676     #&daemon_log("0 DEBUG: $sql", 7);
1677     my $res = $messaging_db->exec_statement( $sql_statement );
1678     
1679     # build out msg for each    usr
1680     foreach my $hit (@{$res}) {
1681         my $receiver = @{$hit}[3];
1682         my $msg_id = @{$hit}[0];
1683         my $subject = @{$hit}[1];
1684         my $message = @{$hit}[7];
1686         # resolve usr -> host where usr is logged in
1687         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1688         #&daemon_log("0 DEBUG: $sql", 7);
1689         my $res = $login_users_db->exec_statement($sql);
1691         # reciver is logged in nowhere
1692         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1694                 my $send_succeed = 0;
1695                 foreach my $hit (@$res) {
1696                                 my $receiver_host = @$hit[0];
1697                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1699                                 # fetch key to encrypt msg propperly for usr/host
1700                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1701                                 &daemon_log("0 DEBUG: $sql", 7);
1702                                 my $res = $known_clients_db->select_dbentry($sql);
1704                                 # host is already down
1705                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1707                                 # host is on
1708                                 my $receiver_key = @{@{$res}[0]}[2];
1709                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1710                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1711                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1712                                 if ($error == 0 ) {
1713                                         $send_succeed++ ;
1714                                 }
1715                 }
1717                 if ($send_succeed) {
1718                                 # set outgoing msg at db to deliverd
1719                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1720                                 &daemon_log("0 DEBUG: $sql", 7);
1721                                 my $res = $messaging_db->exec_statement($sql); 
1722                 }
1723         }
1725     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1726     return;
1730 sub watch_for_done_messages {
1731     my ($kernel,$heap) = @_[KERNEL, HEAP];
1733     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1734     #&daemon_log("0 DEBUG: $sql", 7);
1735     my $res = $messaging_db->exec_statement($sql); 
1737     foreach my $hit (@{$res}) {
1738         my $msg_id = @{$hit}[0];
1740         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1741         #&daemon_log("0 DEBUG: $sql", 7); 
1742         my $res = $messaging_db->exec_statement($sql);
1744         # not all usr msgs have been seen till now
1745         if ( ref(@$res[0]) eq "ARRAY") { next; }
1746         
1747         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1748         #&daemon_log("0 DEBUG: $sql", 7);
1749         $res = $messaging_db->exec_statement($sql);
1750     
1751     }
1753     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1754     return;
1758 sub watch_for_old_known_clients {
1759     my ($kernel,$heap) = @_[KERNEL, HEAP];
1761     my $sql_statement = "SELECT * FROM $known_clients_tn";
1762     my $res = $known_clients_db->select_dbentry( $sql_statement );
1764     my $act_time = int(&get_time());
1766     while ( my ($hit_num, $hit) = each %$res) {
1767         my $expired_timestamp = int($hit->{'timestamp'});
1768         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1769         my $dt = DateTime->new( year   => $1,
1770                 month  => $2,
1771                 day    => $3,
1772                 hour   => $4,
1773                 minute => $5,
1774                 second => $6,
1775                 );
1777         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1778         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1779         if ($act_time > $expired_timestamp) {
1780             my $hostname = $hit->{'hostname'};
1781             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1782             my $del_res = $known_clients_db->exec_statement($del_sql);
1784             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1785         }
1787     }
1789     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1793 sub watch_for_next_tasks {
1794     my ($kernel,$heap) = @_[KERNEL, HEAP];
1796     my $sql = "SELECT * FROM $incoming_tn";
1797     my $res = $incoming_db->select_dbentry($sql);
1799     while ( my ($hit_num, $hit) = each %$res) {
1800         my $headertag = $hit->{'headertag'};
1801         if ($headertag =~ /^answer_(\d+)/) {
1802             # do not start processing, this message is for a still running POE::Wheel
1803             next;
1804         }
1805         my $message_id = $hit->{'id'};
1806         $kernel->yield('next_task', $hit);
1808         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1809         my $res = $incoming_db->exec_statement($sql);
1810     }
1812     $kernel->delay_set('watch_for_next_tasks', 1); 
1816 sub get_ldap_handle {
1817         my ($session_id) = @_;
1818         my $heap;
1819         my $ldap_handle;
1821         if (not defined $session_id ) { $session_id = 0 };
1822         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1824         if ($session_id == 0) {
1825                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1826                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1827                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1829         } else {
1830                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1831                 if( defined $session_reference ) {
1832                         $heap = $session_reference->get_heap();
1833                 }
1835                 if (not defined $heap) {
1836                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1837                         return;
1838                 }
1840                 # TODO: This "if" is nonsense, because it doesn't prove that the
1841                 #       used handle is still valid - or if we've to reconnect...
1842                 #if (not exists $heap->{ldap_handle}) {
1843                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1844                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1845                         $heap->{ldap_handle} = $ldap_handle;
1846                 #}
1847         }
1848         return $ldap_handle;
1852 sub change_fai_state {
1853     my ($st, $targets, $session_id) = @_;
1854     $session_id = 0 if not defined $session_id;
1855     # Set FAI state to localboot
1856     my %mapActions= (
1857         reboot    => '',
1858         update    => 'softupdate',
1859         localboot => 'localboot',
1860         reinstall => 'install',
1861         rescan    => '',
1862         wake      => '',
1863         memcheck  => 'memcheck',
1864         sysinfo   => 'sysinfo',
1865         install   => 'install',
1866     );
1868     # Return if this is unknown
1869     if (!exists $mapActions{ $st }){
1870         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1871       return;
1872     }
1874     my $state= $mapActions{ $st };
1876     my $ldap_handle = &get_ldap_handle($session_id);
1877     if( defined($ldap_handle) ) {
1879       # Build search filter for hosts
1880         my $search= "(&(objectClass=GOhard)";
1881         foreach (@{$targets}){
1882             $search.= "(macAddress=$_)";
1883         }
1884         $search.= ")";
1886       # If there's any host inside of the search string, procress them
1887         if (!($search =~ /macAddress/)){
1888             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1889             return;
1890         }
1892       # Perform search for Unit Tag
1893       my $mesg = $ldap_handle->search(
1894           base   => $ldap_base,
1895           scope  => 'sub',
1896           attrs  => ['dn', 'FAIstate', 'objectClass'],
1897           filter => "$search"
1898           );
1900           if ($mesg->count) {
1901                   my @entries = $mesg->entries;
1902                   foreach my $entry (@entries) {
1903                           # Only modify entry if it is not set to '$state'
1904                           if ($entry->get_value("FAIstate") ne "$state"){
1905                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1906                                   my $result;
1907                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1908                                   if (exists $tmp{'FAIobject'}){
1909                                           if ($state eq ''){
1910                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1911                                                           delete => [ FAIstate => [] ] ]);
1912                                           } else {
1913                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1914                                                           replace => [ FAIstate => $state ] ]);
1915                                           }
1916                                   } elsif ($state ne ''){
1917                                           $result= $ldap_handle->modify($entry->dn, changes => [
1918                                                   add     => [ objectClass => 'FAIobject' ],
1919                                                   add     => [ FAIstate => $state ] ]);
1920                                   }
1922                                   # Errors?
1923                                   if ($result->code){
1924                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1925                                   }
1926                           } else {
1927                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1928                           }  
1929                   }
1930           }
1931     # if no ldap handle defined
1932     } else {
1933         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1934     }
1939 sub change_goto_state {
1940     my ($st, $targets, $session_id) = @_;
1941     $session_id = 0  if not defined $session_id;
1943     # Switch on or off?
1944     my $state= $st eq 'active' ? 'active': 'locked';
1946     my $ldap_handle = &get_ldap_handle($session_id);
1947     if( defined($ldap_handle) ) {
1949       # Build search filter for hosts
1950       my $search= "(&(objectClass=GOhard)";
1951       foreach (@{$targets}){
1952         $search.= "(macAddress=$_)";
1953       }
1954       $search.= ")";
1956       # If there's any host inside of the search string, procress them
1957       if (!($search =~ /macAddress/)){
1958         return;
1959       }
1961       # Perform search for Unit Tag
1962       my $mesg = $ldap_handle->search(
1963           base   => $ldap_base,
1964           scope  => 'sub',
1965           attrs  => ['dn', 'gotoMode'],
1966           filter => "$search"
1967           );
1969       if ($mesg->count) {
1970         my @entries = $mesg->entries;
1971         foreach my $entry (@entries) {
1973           # Only modify entry if it is not set to '$state'
1974           if ($entry->get_value("gotoMode") ne $state){
1976             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1977             my $result;
1978             $result= $ldap_handle->modify($entry->dn, changes => [
1979                                                 replace => [ gotoMode => $state ] ]);
1981             # Errors?
1982             if ($result->code){
1983               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1984             }
1986           }
1987         }
1988       }
1990     }
1994 sub run_create_fai_server_db {
1995     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1996     my $session_id = $session->ID;
1997     my $task = POE::Wheel::Run->new(
1998             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1999             StdoutEvent  => "session_run_result",
2000             StderrEvent  => "session_run_debug",
2001             CloseEvent   => "session_run_done",
2002             );
2004     $heap->{task}->{ $task->ID } = $task;
2005     return;
2009 sub create_fai_server_db {
2010     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2011         my $result;
2013         if (not defined $session_id) { $session_id = 0; }
2014     my $ldap_handle = &get_ldap_handle();
2015         if(defined($ldap_handle)) {
2016                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2017                 my $mesg= $ldap_handle->search(
2018                         base   => $ldap_base,
2019                         scope  => 'sub',
2020                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2021                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2022                 );
2023                 if($mesg->{'resultCode'} == 0 &&
2024                    $mesg->count != 0) {
2025                    foreach my $entry (@{$mesg->{entries}}) {
2026                            if($entry->exists('FAIrepository')) {
2027                                    # Add an entry for each Repository configured for server
2028                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2029                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2030                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2031                                                    $result= $fai_server_db->add_dbentry( { 
2032                                                                    table => $table_name,
2033                                                                    primkey => ['server', 'release', 'tag'],
2034                                                                    server => $tmp_url,
2035                                                                    release => $tmp_release,
2036                                                                    sections => $tmp_sections,
2037                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2038                                                            } );
2039                                            }
2040                                    }
2041                            }
2042                    }
2043                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2045                 # TODO: Find a way to post the 'create_packages_list_db' event
2046                 if(not defined($dont_create_packages_list)) {
2047                         &create_packages_list_db(undef, undef, $session_id);
2048                 }
2049         }       
2050     
2051     $ldap_handle->disconnect;
2052         return $result;
2056 sub run_create_fai_release_db {
2057     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2058         my $session_id = $session->ID;
2059     my $task = POE::Wheel::Run->new(
2060             Program => sub { &create_fai_release_db($table_name, $session_id) },
2061             StdoutEvent  => "session_run_result",
2062             StderrEvent  => "session_run_debug",
2063             CloseEvent   => "session_run_done",
2064             );
2066     $heap->{task}->{ $task->ID } = $task;
2067     return;
2071 sub create_fai_release_db {
2072         my ($table_name, $session_id) = @_;
2073         my $result;
2075     # used for logging
2076     if (not defined $session_id) { $session_id = 0; }
2078     my $ldap_handle = &get_ldap_handle();
2079         if(defined($ldap_handle)) {
2080                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2081                 my $mesg= $ldap_handle->search(
2082                         base   => $ldap_base,
2083                         scope  => 'sub',
2084                         attrs  => [],
2085                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2086                 );
2087                 if($mesg->{'resultCode'} == 0 &&
2088                         $mesg->count != 0) {
2089                         # Walk through all possible FAI container ou's
2090                         my @sql_list;
2091                         my $timestamp= &get_time();
2092                         foreach my $ou (@{$mesg->{entries}}) {
2093                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2094                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2095                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2096                                         if(@tmp_array) {
2097                                                 foreach my $entry (@tmp_array) {
2098                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2099                                                                 my $sql= 
2100                                                                 "INSERT INTO $table_name "
2101                                                                 ."(timestamp, release, class, type, state) VALUES ("
2102                                                                 .$timestamp.","
2103                                                                 ."'".$entry->{'release'}."',"
2104                                                                 ."'".$entry->{'class'}."',"
2105                                                                 ."'".$entry->{'type'}."',"
2106                                                                 ."'".$entry->{'state'}."')";
2107                                                                 push @sql_list, $sql;
2108                                                         }
2109                                                 }
2110                                         }
2111                                 }
2112                         }
2114                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2115                         if(@sql_list) {
2116                                 unshift @sql_list, "VACUUM";
2117                                 unshift @sql_list, "DELETE FROM $table_name";
2118                                 $fai_release_db->exec_statementlist(\@sql_list);
2119                         }
2120                         daemon_log("$session_id DEBUG: Done with inserting",7);
2121                 }
2122                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2123         }
2124     $ldap_handle->disconnect;
2125         return $result;
2128 sub get_fai_types {
2129         my $tmp_classes = shift || return undef;
2130         my @result;
2132         foreach my $type(keys %{$tmp_classes}) {
2133                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2134                         my $entry = {
2135                                 type => $type,
2136                                 state => $tmp_classes->{$type}[0],
2137                         };
2138                         push @result, $entry;
2139                 }
2140         }
2142         return @result;
2145 sub get_fai_state {
2146         my $result = "";
2147         my $tmp_classes = shift || return $result;
2149         foreach my $type(keys %{$tmp_classes}) {
2150                 if(defined($tmp_classes->{$type}[0])) {
2151                         $result = $tmp_classes->{$type}[0];
2152                         
2153                 # State is equal for all types in class
2154                         last;
2155                 }
2156         }
2158         return $result;
2161 sub resolve_fai_classes {
2162         my ($fai_base, $ldap_handle, $session_id) = @_;
2163         if (not defined $session_id) { $session_id = 0; }
2164         my $result;
2165         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2166         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2167         my $fai_classes;
2169         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2170         my $mesg= $ldap_handle->search(
2171                 base   => $fai_base,
2172                 scope  => 'sub',
2173                 attrs  => ['cn','objectClass','FAIstate'],
2174                 filter => $fai_filter,
2175         );
2176         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2178         if($mesg->{'resultCode'} == 0 &&
2179                 $mesg->count != 0) {
2180                 foreach my $entry (@{$mesg->{entries}}) {
2181                         if($entry->exists('cn')) {
2182                                 my $tmp_dn= $entry->dn();
2184                                 # Skip classname and ou dn parts for class
2185                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2187                                 # Skip classes without releases
2188                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2189                                         next;
2190                                 }
2192                                 my $tmp_cn= $entry->get_value('cn');
2193                                 my $tmp_state= $entry->get_value('FAIstate');
2195                                 my $tmp_type;
2196                                 # Get FAI type
2197                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2198                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2199                                                 $tmp_type= $oclass;
2200                                                 last;
2201                                         }
2202                                 }
2204                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2205                                         # A Subrelease
2206                                         my @sub_releases = split(/,/, $tmp_release);
2208                                         # Walk through subreleases and build hash tree
2209                                         my $hash;
2210                                         while(my $tmp_sub_release = pop @sub_releases) {
2211                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2212                                         }
2213                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2214                                 } else {
2215                                         # A branch, no subrelease
2216                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2217                                 }
2218                         } elsif (!$entry->exists('cn')) {
2219                                 my $tmp_dn= $entry->dn();
2220                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2222                                 # Skip classes without releases
2223                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2224                                         next;
2225                                 }
2227                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2228                                         # A Subrelease
2229                                         my @sub_releases= split(/,/, $tmp_release);
2231                                         # Walk through subreleases and build hash tree
2232                                         my $hash;
2233                                         while(my $tmp_sub_release = pop @sub_releases) {
2234                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2235                                         }
2236                                         # Remove the last two characters
2237                                         chop($hash);
2238                                         chop($hash);
2240                                         eval('$fai_classes->'.$hash.'= {}');
2241                                 } else {
2242                                         # A branch, no subrelease
2243                                         if(!exists($fai_classes->{$tmp_release})) {
2244                                                 $fai_classes->{$tmp_release} = {};
2245                                         }
2246                                 }
2247                         }
2248                 }
2250                 # The hash is complete, now we can honor the copy-on-write based missing entries
2251                 foreach my $release (keys %$fai_classes) {
2252                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2253                 }
2254         }
2255         return $result;
2258 sub apply_fai_inheritance {
2259        my $fai_classes = shift || return {};
2260        my $tmp_classes;
2262        # Get the classes from the branch
2263        foreach my $class (keys %{$fai_classes}) {
2264                # Skip subreleases
2265                if($class =~ /^ou=.*$/) {
2266                        next;
2267                } else {
2268                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2269                }
2270        }
2272        # Apply to each subrelease
2273        foreach my $subrelease (keys %{$fai_classes}) {
2274                if($subrelease =~ /ou=/) {
2275                        foreach my $tmp_class (keys %{$tmp_classes}) {
2276                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2277                                        $fai_classes->{$subrelease}->{$tmp_class} =
2278                                        deep_copy($tmp_classes->{$tmp_class});
2279                                } else {
2280                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2281                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2282                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2283                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2284                                                }
2285                                        }
2286                                }
2287                        }
2288                }
2289        }
2291        # Find subreleases in deeper levels
2292        foreach my $subrelease (keys %{$fai_classes}) {
2293                if($subrelease =~ /ou=/) {
2294                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2295                                if($subsubrelease =~ /ou=/) {
2296                                        apply_fai_inheritance($fai_classes->{$subrelease});
2297                                }
2298                        }
2299                }
2300        }
2302        return $fai_classes;
2305 sub get_fai_release_entries {
2306         my $tmp_classes = shift || return;
2307         my $parent = shift || "";
2308         my @result = shift || ();
2310         foreach my $entry (keys %{$tmp_classes}) {
2311                 if(defined($entry)) {
2312                         if($entry =~ /^ou=.*$/) {
2313                                 my $release_name = $entry;
2314                                 $release_name =~ s/ou=//g;
2315                                 if(length($parent)>0) {
2316                                         $release_name = $parent."/".$release_name;
2317                                 }
2318                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2319                                 foreach my $bufentry(@bufentries) {
2320                                         push @result, $bufentry;
2321                                 }
2322                         } else {
2323                                 my @types = get_fai_types($tmp_classes->{$entry});
2324                                 foreach my $type (@types) {
2325                                         push @result, 
2326                                         {
2327                                                 'class' => $entry,
2328                                                 'type' => $type->{'type'},
2329                                                 'release' => $parent,
2330                                                 'state' => $type->{'state'},
2331                                         };
2332                                 }
2333                         }
2334                 }
2335         }
2337         return @result;
2340 sub deep_copy {
2341         my $this = shift;
2342         if (not ref $this) {
2343                 $this;
2344         } elsif (ref $this eq "ARRAY") {
2345                 [map deep_copy($_), @$this];
2346         } elsif (ref $this eq "HASH") {
2347                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2348         } else { die "what type is $_?" }
2352 sub session_run_result {
2353     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2354     $kernel->sig(CHLD => "child_reap");
2357 sub session_run_debug {
2358     my $result = $_[ARG0];
2359     print STDERR "$result\n";
2362 sub session_run_done {
2363     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2364     delete $heap->{task}->{$task_id};
2368 sub create_sources_list {
2369         my $session_id = shift;
2370         my $ldap_handle = &main::get_ldap_handle;
2371         my $result="/tmp/gosa_si_tmp_sources_list";
2373         # Remove old file
2374         if(stat($result)) {
2375                 unlink($result);
2376                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2377         }
2379         my $fh;
2380         open($fh, ">$result");
2381         if (not defined $fh) {
2382                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2383                 return undef;
2384         }
2385         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2386                 my $mesg=$ldap_handle->search(
2387                         base    => $main::ldap_server_dn,
2388                         scope   => 'base',
2389                         attrs   => 'FAIrepository',
2390                         filter  => 'objectClass=FAIrepositoryServer'
2391                 );
2392                 if($mesg->count) {
2393                         foreach my $entry(@{$mesg->{'entries'}}) {
2394                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2395                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2396                                         my $line = "deb $server $release";
2397                                         $sections =~ s/,/ /g;
2398                                         $line.= " $sections";
2399                                         print $fh $line."\n";
2400                                 }
2401                         }
2402                 }
2403         } else {
2404                 if (defined $main::ldap_server_dn){
2405                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2406                 } else {
2407                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2408                 }
2409         }
2410         close($fh);
2412         return $result;
2416 sub run_create_packages_list_db {
2417     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2418         my $session_id = $session->ID;
2420         my $task = POE::Wheel::Run->new(
2421                                         Priority => +20,
2422                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2423                                         StdoutEvent  => "session_run_result",
2424                                         StderrEvent  => "session_run_debug",
2425                                         CloseEvent   => "session_run_done",
2426                                         );
2427         $heap->{task}->{ $task->ID } = $task;
2431 sub create_packages_list_db {
2432         my ($ldap_handle, $sources_file, $session_id) = @_;
2433         
2434         # it should not be possible to trigger a recreation of packages_list_db
2435         # while packages_list_db is under construction, so set flag packages_list_under_construction
2436         # which is tested befor recreation can be started
2437         if (-r $packages_list_under_construction) {
2438                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2439                 return;
2440         } else {
2441                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2442                 # set packages_list_under_construction to true
2443                 system("touch $packages_list_under_construction");
2444                 @packages_list_statements=();
2445         }
2447         if (not defined $session_id) { $session_id = 0; }
2448         if (not defined $ldap_handle) { 
2449                 $ldap_handle= &get_ldap_handle();
2451                 if (not defined $ldap_handle) {
2452                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2453                         unlink($packages_list_under_construction);
2454                         return;
2455                 }
2456         }
2457         if (not defined $sources_file) { 
2458                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2459                 $sources_file = &create_sources_list($session_id);
2460         }
2462         if (not defined $sources_file) {
2463                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2464                 unlink($packages_list_under_construction);
2465                 return;
2466         }
2468         my $line;
2470         open(CONFIG, "<$sources_file") or do {
2471                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2472                 unlink($packages_list_under_construction);
2473                 return;
2474         };
2476         # Read lines
2477         while ($line = <CONFIG>){
2478                 # Unify
2479                 chop($line);
2480                 $line =~ s/^\s+//;
2481                 $line =~ s/^\s+/ /;
2483                 # Strip comments
2484                 $line =~ s/#.*$//g;
2486                 # Skip empty lines
2487                 if ($line =~ /^\s*$/){
2488                         next;
2489                 }
2491                 # Interpret deb line
2492                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2493                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2494                         my $section;
2495                         foreach $section (split(' ', $sections)){
2496                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2497                         }
2498                 }
2499         }
2501         close (CONFIG);
2503         find(\&cleanup_and_extract, keys( %repo_dirs ));
2504         &main::strip_packages_list_statements();
2505         unshift @packages_list_statements, "VACUUM";
2506         $packages_list_db->exec_statementlist(\@packages_list_statements);
2507         unlink($packages_list_under_construction);
2508         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2509         return;
2512 # This function should do some intensive task to minimize the db-traffic
2513 sub strip_packages_list_statements {
2514     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2515         my @new_statement_list=();
2516         my $hash;
2517         my $insert_hash;
2518         my $update_hash;
2519         my $delete_hash;
2520         my $local_timestamp=get_time();
2522         foreach my $existing_entry (@existing_entries) {
2523                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2524         }
2526         foreach my $statement (@packages_list_statements) {
2527                 if($statement =~ /^INSERT/i) {
2528                         # Assign the values from the insert statement
2529                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2530                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2531                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2532                                 # If section or description has changed, update the DB
2533                                 if( 
2534                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2535                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2536                                 ) {
2537                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2538                                 }
2539                         } else {
2540                                 # Insert a non-existing entry to db
2541                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2542                         }
2543                 } elsif ($statement =~ /^UPDATE/i) {
2544                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2545                         /^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;
2546                         foreach my $distribution (keys %{$hash}) {
2547                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2548                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2549                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2550                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2551                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2552                                                 my $section;
2553                                                 my $description;
2554                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2555                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2556                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2557                                                 }
2558                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2559                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2560                                                 }
2561                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2562                                         }
2563                                 }
2564                         }
2565                 }
2566         }
2568         # TODO: Check for orphaned entries
2570         # unroll the insert_hash
2571         foreach my $distribution (keys %{$insert_hash}) {
2572                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2573                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2574                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2575                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2576                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2577                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2578                                 ."'$local_timestamp')";
2579                         }
2580                 }
2581         }
2583         # unroll the update hash
2584         foreach my $distribution (keys %{$update_hash}) {
2585                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2586                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2587                                 my $set = "";
2588                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2589                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2590                                 }
2591                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2592                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2593                                 }
2594                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2595                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2596                                 }
2597                                 if(defined($set) and length($set) > 0) {
2598                                         $set .= "timestamp = '$local_timestamp'";
2599                                 } else {
2600                                         next;
2601                                 }
2602                                 push @new_statement_list, 
2603                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2604                                         ." distribution = '$distribution'"
2605                                         ." AND package = '$package'"
2606                                         ." AND version = '$version'";
2607                         }
2608                 }
2609         }
2611         @packages_list_statements = @new_statement_list;
2615 sub parse_package_info {
2616     my ($baseurl, $dist, $section, $session_id)= @_;
2617     my ($package);
2618     if (not defined $session_id) { $session_id = 0; }
2619     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2620     $repo_dirs{ "${repo_path}/pool" } = 1;
2622     foreach $package ("Packages.gz"){
2623         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2624         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2625         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2626     }
2627     
2631 sub get_package {
2632     my ($url, $dest, $session_id)= @_;
2633     if (not defined $session_id) { $session_id = 0; }
2635     my $tpath = dirname($dest);
2636     -d "$tpath" || mkpath "$tpath";
2638     # This is ugly, but I've no time to take a look at "how it works in perl"
2639     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2640         system("gunzip -cd '$dest' > '$dest.in'");
2641         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2642         unlink($dest);
2643         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2644     } else {
2645         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2646     }
2647     return 0;
2651 sub parse_package {
2652     my ($path, $dist, $srv_path, $session_id)= @_;
2653     if (not defined $session_id) { $session_id = 0;}
2654     my ($package, $version, $section, $description);
2655     my $PACKAGES;
2656     my $timestamp = &get_time();
2658     if(not stat("$path.in")) {
2659         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2660         return;
2661     }
2663     open($PACKAGES, "<$path.in");
2664     if(not defined($PACKAGES)) {
2665         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2666         return;
2667     }
2669     # Read lines
2670     while (<$PACKAGES>){
2671         my $line = $_;
2672         # Unify
2673         chop($line);
2675         # Use empty lines as a trigger
2676         if ($line =~ /^\s*$/){
2677             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2678             push(@packages_list_statements, $sql);
2679             $package = "none";
2680             $version = "none";
2681             $section = "none";
2682             $description = "none"; 
2683             next;
2684         }
2686         # Trigger for package name
2687         if ($line =~ /^Package:\s/){
2688             ($package)= ($line =~ /^Package: (.*)$/);
2689             next;
2690         }
2692         # Trigger for version
2693         if ($line =~ /^Version:\s/){
2694             ($version)= ($line =~ /^Version: (.*)$/);
2695             next;
2696         }
2698         # Trigger for description
2699         if ($line =~ /^Description:\s/){
2700             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2701             next;
2702         }
2704         # Trigger for section
2705         if ($line =~ /^Section:\s/){
2706             ($section)= ($line =~ /^Section: (.*)$/);
2707             next;
2708         }
2710         # Trigger for filename
2711         if ($line =~ /^Filename:\s/){
2712             my ($filename) = ($line =~ /^Filename: (.*)$/);
2713             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2714             next;
2715         }
2716     }
2718     close( $PACKAGES );
2719     unlink( "$path.in" );
2720     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2724 sub store_fileinfo {
2725     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2727     my %fileinfo = (
2728         'package' => $package,
2729         'dist' => $dist,
2730         'version' => $vers,
2731     );
2733     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2737 sub cleanup_and_extract {
2738     my $fileinfo = $repo_files{ $File::Find::name };
2740     if( defined $fileinfo ) {
2742         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2743         my $sql;
2744         my $package = $fileinfo->{ 'package' };
2745         my $newver = $fileinfo->{ 'version' };
2747         mkpath($dir);
2748         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2750                 if( -f "$dir/DEBIAN/templates" ) {
2752                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2754                         my $tmpl= "";
2755                         {
2756                                 local $/=undef;
2757                                 open FILE, "$dir/DEBIAN/templates";
2758                                 $tmpl = &encode_base64(<FILE>);
2759                                 close FILE;
2760                         }
2761                         rmtree("$dir/DEBIAN/templates");
2763                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2764                 push @packages_list_statements, $sql;
2765                 }
2766     }
2768     return;
2772 sub register_at_foreign_servers {   
2773     my ($kernel) = $_[KERNEL];
2775     # hole alle bekannten server aus known_server_db
2776     my $server_sql = "SELECT * FROM $known_server_tn";
2777     my $server_res = $known_server_db->exec_statement($server_sql);
2779     # no entries in known_server_db
2780     if (not ref(@$server_res[0]) eq "ARRAY") { 
2781         # TODO
2782     }
2784     # detect already connected clients
2785     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2786     my $client_res = $known_clients_db->exec_statement($client_sql);
2788     # send my server details to all other gosa-si-server within the network
2789     foreach my $hit (@$server_res) {
2790         my $hostname = @$hit[0];
2791         my $hostkey = &create_passwd;
2793         # add already connected clients to registration message 
2794         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2795         &add_content2xml_hash($myhash, 'key', $hostkey);
2796         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2797         
2798         # build registration message and send it
2799         my $foreign_server_msg = &create_xml_string($myhash);
2800         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2801     }
2802     
2803     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2804     return;
2808 #==== MAIN = main ==============================================================
2809 #  parse commandline options
2810 Getopt::Long::Configure( "bundling" );
2811 GetOptions("h|help" => \&usage,
2812         "c|config=s" => \$cfg_file,
2813         "f|foreground" => \$foreground,
2814         "v|verbose+" => \$verbose,
2815         "no-bus+" => \$no_bus,
2816         "no-arp+" => \$no_arp,
2817            );
2819 #  read and set config parameters
2820 &check_cmdline_param ;
2821 &read_configfile;
2822 &check_pid;
2824 $SIG{CHLD} = 'IGNORE';
2826 # forward error messages to logfile
2827 if( ! $foreground ) {
2828   open( STDIN,  '+>/dev/null' );
2829   open( STDOUT, '+>&STDIN'    );
2830   open( STDERR, '+>&STDIN'    );
2833 # Just fork, if we are not in foreground mode
2834 if( ! $foreground ) { 
2835     chdir '/'                 or die "Can't chdir to /: $!";
2836     $pid = fork;
2837     setsid                    or die "Can't start a new session: $!";
2838     umask 0;
2839 } else { 
2840     $pid = $$; 
2843 # Do something useful - put our PID into the pid_file
2844 if( 0 != $pid ) {
2845     open( LOCK_FILE, ">$pid_file" );
2846     print LOCK_FILE "$pid\n";
2847     close( LOCK_FILE );
2848     if( !$foreground ) { 
2849         exit( 0 ) 
2850     };
2853 # parse head url and revision from svn
2854 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2855 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2856 $server_headURL = defined $1 ? $1 : 'unknown' ;
2857 $server_revision = defined $2 ? $2 : 'unknown' ;
2858 if ($server_headURL =~ /\/tag\// || 
2859         $server_headURL =~ /\/branches\// ) {
2860     $server_status = "stable"; 
2861 } else {
2862     $server_status = "developmental" ;
2866 daemon_log(" ", 1);
2867 daemon_log("$0 started!", 1);
2868 daemon_log("status: $server_status", 1);
2869 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2871 if ($no_bus > 0) {
2872     $bus_activ = "false"
2875 # connect to incoming_db
2876 unlink($incoming_file_name);
2877 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2878 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2880 # connect to gosa-si job queue
2881 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2882 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2884 # connect to known_clients_db
2885 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2886 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2888 # connect to foreign_clients_db
2889 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2890 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2892 # connect to known_server_db
2893 unlink($known_server_file_name);
2894 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2895 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2897 # connect to login_usr_db
2898 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2899 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2901 # connect to fai_server_db and fai_release_db
2902 unlink($fai_server_file_name);
2903 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2904 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2906 unlink($fai_release_file_name);
2907 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2908 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2910 # connect to packages_list_db
2911 #unlink($packages_list_file_name);
2912 unlink($packages_list_under_construction);
2913 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2914 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2916 # connect to messaging_db
2917 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2918 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2921 # create xml object used for en/decrypting
2922 $xml = new XML::Simple();
2925 # foreign servers 
2926 my @foreign_server_list;
2928 # add foreign server from cfg file
2929 if ($foreign_server_string ne "") {
2930     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2931     foreach my $foreign_server (@cfg_foreign_server_list) {
2932         push(@foreign_server_list, $foreign_server);
2933     }
2936 # add foreign server from dns
2937 my @tmp_servers;
2938 if ( !$server_domain) {
2939     # Try our DNS Searchlist
2940     for my $domain(get_dns_domains()) {
2941         chomp($domain);
2942         my @tmp_domains= &get_server_addresses($domain);
2943         if(@tmp_domains) {
2944             for my $tmp_server(@tmp_domains) {
2945                 push @tmp_servers, $tmp_server;
2946             }
2947         }
2948     }
2949     if(@tmp_servers && length(@tmp_servers)==0) {
2950         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2951     }
2952 } else {
2953     @tmp_servers = &get_server_addresses($server_domain);
2954     if( 0 == @tmp_servers ) {
2955         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2956     }
2958 foreach my $server (@tmp_servers) { 
2959     unshift(@foreign_server_list, $server); 
2961 # eliminate duplicate entries
2962 @foreign_server_list = &del_doubles(@foreign_server_list);
2963 my $all_foreign_server = join(", ", @foreign_server_list);
2964 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2966 # add all found foreign servers to known_server
2967 my $act_timestamp = &get_time();
2968 foreach my $foreign_server (@foreign_server_list) {
2969     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2970             primkey=>['hostname'],
2971             hostname=>$foreign_server,
2972             status=>'not_jet_registered',
2973             hostkey=>"none",
2974             timestamp=>$act_timestamp,
2975             } );
2979 POE::Component::Server::TCP->new(
2980     Alias => "TCP_SERVER",
2981         Port => $server_port,
2982         ClientInput => sub {
2983         my ($kernel, $input) = @_[KERNEL, ARG0];
2984         push(@tasks, $input);
2985         push(@msgs_to_decrypt, $input);
2986         $kernel->yield("msg_to_decrypt");
2987         },
2988     InlineStates => {
2989         msg_to_decrypt => \&msg_to_decrypt,
2990         next_task => \&next_task,
2991         task_result => \&handle_task_result,
2992         task_done   => \&handle_task_done,
2993         task_debug  => \&handle_task_debug,
2994         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2995     }
2996 );
2998 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3000 # create session for repeatedly checking the job queue for jobs
3001 POE::Session->create(
3002         inline_states => {
3003                 _start => \&session_start,
3004         register_at_foreign_servers => \&register_at_foreign_servers,
3005         sig_handler => \&sig_handler,
3006         next_task => \&next_task,
3007         task_result => \&handle_task_result,
3008         task_done   => \&handle_task_done,
3009         task_debug  => \&handle_task_debug,
3010         watch_for_next_tasks => \&watch_for_next_tasks,
3011         watch_for_new_messages => \&watch_for_new_messages,
3012         watch_for_delivery_messages => \&watch_for_delivery_messages,
3013         watch_for_done_messages => \&watch_for_done_messages,
3014                 watch_for_new_jobs => \&watch_for_new_jobs,
3015         watch_for_done_jobs => \&watch_for_done_jobs,
3016         watch_for_old_known_clients => \&watch_for_old_known_clients,
3017         create_packages_list_db => \&run_create_packages_list_db,
3018         create_fai_server_db => \&run_create_fai_server_db,
3019         create_fai_release_db => \&run_create_fai_release_db,
3020         session_run_result => \&session_run_result,
3021         session_run_debug => \&session_run_debug,
3022         session_run_done => \&session_run_done,
3023         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3024         }
3025 );
3028 # import all modules
3029 &import_modules;
3031 # TODO
3032 # check wether all modules are gosa-si valid passwd check
3036 POE::Kernel->run();
3037 exit;