Code

Updated setup migration step
[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 ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file, 
85     $server_ip, $server_port, $ClientPackages_key, 
86     $arp_activ, $gosa_unit_tag,
87     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $gosa_address;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
101 # dak variables
102 our $dak_base_directory;
103 our $dak_signing_keys_directory;
104 our $dak_user;
106 # specifies the verbosity of the daemon_log
107 $verbose = 0 ;
109 # if foreground is not null, script will be not forked to background
110 $foreground = 0 ;
112 # specifies the timeout seconds while checking the online status of a registrating client
113 $ping_timeout = 5;
115 $no_arp = 0;
116 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
117 my @packages_list_statements;
118 my $watch_for_new_jobs_in_progress = 0;
120 # holds all incoming decrypted messages
121 our $incoming_db;
122 our $incoming_tn = 'incoming';
123 my $incoming_file_name;
124 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
125         "timestamp DEFAULT 'none'", 
126         "headertag DEFAULT 'none'",
127                 "targettag DEFAULT 'none'",
128         "xmlmessage DEFAULT 'none'",
129         "module DEFAULT 'none'",
130         "sessionid DEFAULT '0'",
131         );
133 # holds all gosa jobs
134 our $job_db;
135 our $job_queue_tn = 'jobs';
136 my $job_queue_file_name;
137 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
138                 "timestamp DEFAULT 'none'", 
139                 "status DEFAULT 'none'", 
140                 "result DEFAULT 'none'", 
141                 "progress DEFAULT 'none'", 
142         "headertag DEFAULT 'none'", 
143                 "targettag DEFAULT 'none'", 
144                 "xmlmessage DEFAULT 'none'", 
145                 "macaddress DEFAULT 'none'",
146                 "plainname DEFAULT 'none'",
147                 );
149 # holds all other gosa-si-server
150 our $known_server_db;
151 our $known_server_tn = "known_server";
152 my $known_server_file_name;
153 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
155 # holds all registrated clients
156 our $known_clients_db;
157 our $known_clients_tn = "known_clients";
158 my $known_clients_file_name;
159 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
161 # holds all registered clients at a foreign server
162 our $foreign_clients_db;
163 our $foreign_clients_tn = "foreign_clients"; 
164 my $foreign_clients_file_name;
165 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
167 # holds all logged in user at each client 
168 our $login_users_db;
169 our $login_users_tn = "login_users";
170 my $login_users_file_name;
171 my @login_users_col_names = ("client", "user", "timestamp");
173 # holds all fai server, the debian release and tag
174 our $fai_server_db;
175 our $fai_server_tn = "fai_server"; 
176 my $fai_server_file_name;
177 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
179 our $fai_release_db;
180 our $fai_release_tn = "fai_release"; 
181 my $fai_release_file_name;
182 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
184 # holds all packages available from different repositories
185 our $packages_list_db;
186 our $packages_list_tn = "packages_list";
187 my $packages_list_file_name;
188 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
189 my $outdir = "/tmp/packages_list_db";
190 my $arch = "i386"; 
192 # holds all messages which should be delivered to a user
193 our $messaging_db;
194 our $messaging_tn = "messaging"; 
195 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
196         "flag", "direction", "delivery_time", "message", "timestamp" );
197 my $messaging_file_name;
199 # path to directory to store client install log files
200 our $client_fai_log_dir = "/var/log/fai"; 
202 # queue which stores taskes until one of the $max_children children are ready to process the task
203 my @tasks = qw();
204 my @msgs_to_decrypt = qw();
205 my $max_children = 2;
208 %cfg_defaults = (
209 "general" => {
210     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
211     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
212     },
213 "server" => {
214     "port" => [\$server_port, "20081"],
215     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
216     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
217     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
218     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
219     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
220     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
221     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
222     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
223     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
224     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
225     "repo-path"            => [\$repo_path, '/srv/www/repository'],
226     "ldap-uri"             => [\$ldap_uri, ""],
227     "ldap-base"            => [\$ldap_base, ""],
228     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
229     "ldap-admin-password"  => [\$ldap_admin_password, ""],
230     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
231     "max-clients"          => [\$max_clients, 10],
232     },
233 "GOsaPackages" => {
234     "ip" => [\$gosa_ip, "0.0.0.0"],
235     "port" => [\$gosa_port, "20082"],
236     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
237     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
238     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
239     "key" => [\$GosaPackages_key, "none"],
240         "dak-base" => [\$dak_base_directory, "/srv/archive"],
241         "dak-keyring" => [\$dak_signing_keys_directory, "/srv/archive/s3kr1t"],
242         "dak-user" => [\$dak_user, "deb-dak"],
243     },
244 "ClientPackages" => {
245     "key" => [\$ClientPackages_key, "none"],
246     },
247 "ServerPackages"=> {
248     "address"      => [\$foreign_server_string, ""],
249     "domain"  => [\$server_domain, ""],
250     "key"     => [\$ServerPackages_key, "none"],
251     "key-lifetime" => [\$foreign_servers_register_delay, 120],
253 );
256 #===  FUNCTION  ================================================================
257 #         NAME:  usage
258 #   PARAMETERS:  nothing
259 #      RETURNS:  nothing
260 #  DESCRIPTION:  print out usage text to STDERR
261 #===============================================================================
262 sub usage {
263     print STDERR << "EOF" ;
264 usage: $prg [-hvf] [-c config]
266            -h        : this (help) message
267            -c <file> : config file
268            -f        : foreground, process will not be forked to background
269            -v        : be verbose (multiple to increase verbosity)
270            -no-arp   : starts $prg without connection to arp module
271  
272 EOF
273     print "\n" ;
277 #===  FUNCTION  ================================================================
278 #         NAME:  read_configfile
279 #   PARAMETERS:  cfg_file - string -
280 #      RETURNS:  nothing
281 #  DESCRIPTION:  read cfg_file and set variables
282 #===============================================================================
283 sub read_configfile {
284     my $cfg;
285     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
286         if( -r $cfg_file ) {
287             $cfg = Config::IniFiles->new( -file => $cfg_file );
288         } else {
289             print STDERR "Couldn't read config file!\n";
290         }
291     } else {
292         $cfg = Config::IniFiles->new() ;
293     }
294     foreach my $section (keys %cfg_defaults) {
295         foreach my $param (keys %{$cfg_defaults{ $section }}) {
296             my $pinfo = $cfg_defaults{ $section }{ $param };
297             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
298         }
299     }
303 #===  FUNCTION  ================================================================
304 #         NAME:  logging
305 #   PARAMETERS:  level - string - default 'info'
306 #                msg - string -
307 #                facility - string - default 'LOG_DAEMON'
308 #      RETURNS:  nothing
309 #  DESCRIPTION:  function for logging
310 #===============================================================================
311 sub daemon_log {
312     # log into log_file
313     my( $msg, $level ) = @_;
314     if(not defined $msg) { return }
315     if(not defined $level) { $level = 1 }
316     if(defined $log_file){
317         open(LOG_HANDLE, ">>$log_file");
318         chmod 0600, $log_file;
319         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
320             print STDERR "cannot open $log_file: $!";
321             return 
322         }
323         chomp($msg);
324         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
325         if($level <= $verbose){
326             my ($seconds, $minutes, $hours, $monthday, $month,
327                     $year, $weekday, $yearday, $sommertime) = localtime(time);
328             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
329             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
330             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
331             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
332             $month = $monthnames[$month];
333             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
334             $year+=1900;
335             my $name = $prg;
337             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
338             print LOG_HANDLE $log_msg;
339             if( $foreground ) { 
340                 print STDERR $log_msg;
341             }
342         }
343         close( LOG_HANDLE );
344     }
348 #===  FUNCTION  ================================================================
349 #         NAME:  check_cmdline_param
350 #   PARAMETERS:  nothing
351 #      RETURNS:  nothing
352 #  DESCRIPTION:  validates commandline parameter
353 #===============================================================================
354 sub check_cmdline_param () {
355     my $err_config;
356     my $err_counter = 0;
357         if(not defined($cfg_file)) {
358                 $cfg_file = "/etc/gosa-si/server.conf";
359                 if(! -r $cfg_file) {
360                         $err_config = "please specify a config file";
361                         $err_counter += 1;
362                 }
363     }
364     if( $err_counter > 0 ) {
365         &usage( "", 1 );
366         if( defined( $err_config)) { print STDERR "$err_config\n"}
367         print STDERR "\n";
368         exit( -1 );
369     }
373 #===  FUNCTION  ================================================================
374 #         NAME:  check_pid
375 #   PARAMETERS:  nothing
376 #      RETURNS:  nothing
377 #  DESCRIPTION:  handels pid processing
378 #===============================================================================
379 sub check_pid {
380     $pid = -1;
381     # Check, if we are already running
382     if( open(LOCK_FILE, "<$pid_file") ) {
383         $pid = <LOCK_FILE>;
384         if( defined $pid ) {
385             chomp( $pid );
386             if( -f "/proc/$pid/stat" ) {
387                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
388                 if( $stat ) {
389                                         daemon_log("ERROR: Already running",1);
390                     close( LOCK_FILE );
391                     exit -1;
392                 }
393             }
394         }
395         close( LOCK_FILE );
396         unlink( $pid_file );
397     }
399     # create a syslog msg if it is not to possible to open PID file
400     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
401         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
402         if (open(LOCK_FILE, '<', $pid_file)
403                 && ($pid = <LOCK_FILE>))
404         {
405             chomp($pid);
406             $msg .= "(PID $pid)\n";
407         } else {
408             $msg .= "(unable to read PID)\n";
409         }
410         if( ! ($foreground) ) {
411             openlog( $0, "cons,pid", "daemon" );
412             syslog( "warning", $msg );
413             closelog();
414         }
415         else {
416             print( STDERR " $msg " );
417         }
418         exit( -1 );
419     }
422 #===  FUNCTION  ================================================================
423 #         NAME:  import_modules
424 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
425 #                are stored
426 #      RETURNS:  nothing
427 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
428 #                state is on is imported by "require 'file';"
429 #===============================================================================
430 sub import_modules {
431     daemon_log(" ", 1);
433     if (not -e $modules_path) {
434         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
435     }
437     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
438     while (defined (my $file = readdir (DIR))) {
439         if (not $file =~ /(\S*?).pm$/) {
440             next;
441         }
442                 my $mod_name = $1;
444         if( $file =~ /ArpHandler.pm/ ) {
445             if( $no_arp > 0 ) {
446                 next;
447             }
448         }
449         
450         eval { require $file; };
451         if ($@) {
452             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
453             daemon_log("$@", 5);
454                 } else {
455                         my $info = eval($mod_name.'::get_module_info()');
456                         # Only load module if get_module_info() returns a non-null object
457                         if( $info ) {
458                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
459                                 $known_modules->{$mod_name} = $info;
460                                 daemon_log("0 INFO: module $mod_name loaded", 5);
461                         }
462                 }
463     }   
464     close (DIR);
468 #===  FUNCTION  ================================================================
469 #         NAME:  sig_int_handler
470 #   PARAMETERS:  signal - string - signal arose from system
471 #      RETURNS:  noting
472 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
473 #===============================================================================
474 sub sig_int_handler {
475     my ($signal) = @_;
477 #       if (defined($ldap_handle)) {
478 #               $ldap_handle->disconnect;
479 #       }
480     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
481     
483     daemon_log("shutting down gosa-si-server", 1);
484     system("kill `ps -C gosa-si-server -o pid=`");
486 $SIG{INT} = \&sig_int_handler;
489 sub check_key_and_xml_validity {
490     my ($crypted_msg, $module_key, $session_id) = @_;
491     my $msg;
492     my $msg_hash;
493     my $error_string;
494     eval{
495         $msg = &decrypt_msg($crypted_msg, $module_key);
497         if ($msg =~ /<xml>/i){
498             $msg =~ s/\s+/ /g;  # just for better daemon_log
499             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
500             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
502             ##############
503             # check header
504             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
505             my $header_l = $msg_hash->{'header'};
506             if( 1 > @{$header_l} ) { die 'empty header tag'; }
507             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
508             my $header = @{$header_l}[0];
509             if( 0 == length $header) { die 'empty string in header tag'; }
511             ##############
512             # check source
513             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
514             my $source_l = $msg_hash->{'source'};
515             if( 1 > @{$source_l} ) { die 'empty source tag'; }
516             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
517             my $source = @{$source_l}[0];
518             if( 0 == length $source) { die 'source error'; }
520             ##############
521             # check target
522             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
523             my $target_l = $msg_hash->{'target'};
524             if( 1 > @{$target_l} ) { die 'empty target tag'; }
525         }
526     };
527     if($@) {
528         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
529         $msg = undef;
530         $msg_hash = undef;
531     }
533     return ($msg, $msg_hash);
537 sub check_outgoing_xml_validity {
538     my ($msg) = @_;
540     my $msg_hash;
541     eval{
542         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
544         ##############
545         # check header
546         my $header_l = $msg_hash->{'header'};
547         if( 1 != @{$header_l} ) {
548             die 'no or more than one headers specified';
549         }
550         my $header = @{$header_l}[0];
551         if( 0 == length $header) {
552             die 'header has length 0';
553         }
555         ##############
556         # check source
557         my $source_l = $msg_hash->{'source'};
558         if( 1 != @{$source_l} ) {
559             die 'no or more than 1 sources specified';
560         }
561         my $source = @{$source_l}[0];
562         if( 0 == length $source) {
563             die 'source has length 0';
564         }
565         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
566                 $source =~ /^GOSA$/i ) {
567             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
568         }
569         
570         ##############
571         # check target  
572         my $target_l = $msg_hash->{'target'};
573         if( 0 == @{$target_l} ) {
574             die "no targets specified";
575         }
576         foreach my $target (@$target_l) {
577             if( 0 == length $target) {
578                 die "target has length 0";
579             }
580             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
581                     $target =~ /^GOSA$/i ||
582                     $target =~ /^\*$/ ||
583                     $target =~ /KNOWN_SERVER/i ||
584                     $target =~ /JOBDB/i ||
585                     $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 ){
586                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
587             }
588         }
589     };
590     if($@) {
591         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
592         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
593         $msg_hash = undef;
594     }
596     return ($msg_hash);
600 sub input_from_known_server {
601     my ($input, $remote_ip, $session_id) = @_ ;  
602     my ($msg, $msg_hash, $module);
604     my $sql_statement= "SELECT * FROM known_server";
605     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
607     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
608         my $host_name = $hit->{hostname};
609         if( not $host_name =~ "^$remote_ip") {
610             next;
611         }
612         my $host_key = $hit->{hostkey};
613         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
614         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
616         # check if module can open msg envelope with module key
617         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
618         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
619             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
620             daemon_log("$@", 8);
621             next;
622         }
623         else {
624             $msg = $tmp_msg;
625             $msg_hash = $tmp_msg_hash;
626             $module = "ServerPackages";
627             last;
628         }
629     }
631     if( (!$msg) || (!$msg_hash) || (!$module) ) {
632         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
633     }
634   
635     return ($msg, $msg_hash, $module);
639 sub input_from_known_client {
640     my ($input, $remote_ip, $session_id) = @_ ;  
641     my ($msg, $msg_hash, $module);
643     my $sql_statement= "SELECT * FROM known_clients";
644     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
645     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
646         my $host_name = $hit->{hostname};
647         if( not $host_name =~ /^$remote_ip:\d*$/) {
648                 next;
649                 }
650         my $host_key = $hit->{hostkey};
651         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
652         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
654         # check if module can open msg envelope with module key
655         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
657         if( (!$msg) || (!$msg_hash) ) {
658             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
659             &daemon_log("$@", 8);
660             next;
661         }
662         else {
663             $module = "ClientPackages";
664             last;
665         }
666     }
668     if( (!$msg) || (!$msg_hash) || (!$module) ) {
669         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
670     }
672     return ($msg, $msg_hash, $module);
676 sub input_from_unknown_host {
677     no strict "refs";
678     my ($input, $session_id) = @_ ;
679     my ($msg, $msg_hash, $module);
680     my $error_string;
681     
682         my %act_modules = %$known_modules;
683         
684     while( my ($mod, $info) = each(%act_modules)) {
686         # check a key exists for this module
687         my $module_key = ${$mod."_key"};
688         if( not defined $module_key ) {
689             if( $mod eq 'ArpHandler' ) {
690                 next;
691             }
692             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
693             next;
694         }
695         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
697         # check if module can open msg envelope with module key
698         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
699         if( (not defined $msg) || (not defined $msg_hash) ) {
700             next;
701         }
702         else {
703             $module = $mod;
704             last;
705         }
706     }
708     if( (!$msg) || (!$msg_hash) || (!$module)) {
709         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
710     }
712     return ($msg, $msg_hash, $module);
716 sub create_ciphering {
717     my ($passwd) = @_;
718         if((!defined($passwd)) || length($passwd)==0) {
719                 $passwd = "";
720         }
721     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
722     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
723     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
724     $my_cipher->set_iv($iv);
725     return $my_cipher;
729 sub encrypt_msg {
730     my ($msg, $key) = @_;
731     my $my_cipher = &create_ciphering($key);
732     my $len;
733     {
734             use bytes;
735             $len= 16-length($msg)%16;
736     }
737     $msg = "\0"x($len).$msg;
738     $msg = $my_cipher->encrypt($msg);
739     chomp($msg = &encode_base64($msg));
740     # there are no newlines allowed inside msg
741     $msg=~ s/\n//g;
742     return $msg;
746 sub decrypt_msg {
748     my ($msg, $key) = @_ ;
749     $msg = &decode_base64($msg);
750     my $my_cipher = &create_ciphering($key);
751     $msg = $my_cipher->decrypt($msg); 
752     $msg =~ s/\0*//g;
753     return $msg;
757 sub get_encrypt_key {
758     my ($target) = @_ ;
759     my $encrypt_key;
760     my $error = 0;
762     # target can be in known_server
763     if( not defined $encrypt_key ) {
764         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
765         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
766         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
767             my $host_name = $hit->{hostname};
768             if( $host_name ne $target ) {
769                 next;
770             }
771             $encrypt_key = $hit->{hostkey};
772             last;
773         }
774     }
776     # target can be in known_client
777     if( not defined $encrypt_key ) {
778         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
779         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
780         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
781             my $host_name = $hit->{hostname};
782             if( $host_name ne $target ) {
783                 next;
784             }
785             $encrypt_key = $hit->{hostkey};
786             last;
787         }
788     }
790     return $encrypt_key;
794 #===  FUNCTION  ================================================================
795 #         NAME:  open_socket
796 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
797 #                [PeerPort] string necessary if port not appended by PeerAddr
798 #      RETURNS:  socket IO::Socket::INET
799 #  DESCRIPTION:  open a socket to PeerAddr
800 #===============================================================================
801 sub open_socket {
802     my ($PeerAddr, $PeerPort) = @_ ;
803     if(defined($PeerPort)){
804         $PeerAddr = $PeerAddr.":".$PeerPort;
805     }
806     my $socket;
807     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
808             Porto => "tcp",
809             Type => SOCK_STREAM,
810             Timeout => 5,
811             );
812     if(not defined $socket) {
813         return;
814     }
815 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
816     return $socket;
820 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
821 #===  FUNCTION  ================================================================
822 #         NAME:  get_ip 
823 #   PARAMETERS:  interface name (i.e. eth0)
824 #      RETURNS:  (ip address) 
825 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
826 #===============================================================================
827 #sub get_ip {
828 #       my $ifreq= shift;
829 #       my $result= "";
830 #       my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
831 #       my $proto= getprotobyname('ip');
833 #       socket SOCKET, PF_INET, SOCK_DGRAM, $proto
834 #               or die "socket: $!";
836 #       if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
837 #               my ($if, $sin)    = unpack 'a16 a16', $ifreq;
838 #               my ($port, $addr) = sockaddr_in $sin;
839 #               my $ip            = inet_ntoa $addr;
841 #               if ($ip && length($ip) > 0) {
842 #                       $result = $ip;
843 #               }
844 #       }
846 #       return $result;
847 #}
850 sub get_local_ip_for_remote_ip {
851         my $remote_ip= shift;
852         my $result="0.0.0.0";
854         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855                 if($remote_ip eq "127.0.0.1") {
856                         $result = "127.0.0.1";
857                 } else {
858                         my $PROC_NET_ROUTE= ('/proc/net/route');
860                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861                                 or die "Could not open $PROC_NET_ROUTE";
863                         my @ifs = <PROC_NET_ROUTE>;
865                         close(PROC_NET_ROUTE);
867                         # Eat header line
868                         shift @ifs;
869                         chomp @ifs;
870                         foreach my $line(@ifs) {
871                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872                                 my $destination;
873                                 my $mask;
874                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879                                         # destination matches route, save mac and exit
880                                         $result= &get_ip($Iface);
881                                         last;
882                                 }
883                         }
884                 }
885         } else {
886                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
887         }
888         return $result;
892 sub send_msg_to_target {
893     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894     my $error = 0;
895     my $header;
896     my $timestamp = &get_time();
897     my $new_status;
898     my $act_status;
899     my ($sql_statement, $res);
900   
901     if( $msg_header ) {
902         $header = "'$msg_header'-";
903     } else {
904         $header = "";
905     }
907         # Patch the source ip
908         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911         }
913     # encrypt xml msg
914     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916     # opensocket
917     my $socket = &open_socket($address);
918     if( !$socket ) {
919         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
920         $error++;
921     }
922     
923     if( $error == 0 ) {
924         # send xml msg
925         print $socket $crypted_msg."\n";
927         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928         daemon_log("$session_id DEBUG: message:\n$msg", 9);
929         
930     }
932     # close socket in any case
933     if( $socket ) {
934         close $socket;
935     }
937     if( $error > 0 ) { $new_status = "down"; }
938     else { $new_status = $msg_header; }
941     # known_clients
942     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
943     $res = $known_clients_db->select_dbentry($sql_statement);
944     if( keys(%$res) == 1) {
945         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
946         if ($act_status eq "down" && $new_status eq "down") {
947             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948             $res = $known_clients_db->del_dbentry($sql_statement);
949             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950         } else { 
951             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952             $res = $known_clients_db->update_dbentry($sql_statement);
953             if($new_status eq "down"){
954                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955             } else {
956                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957             }
958         }
959     }
961     # known_server
962     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963     $res = $known_server_db->select_dbentry($sql_statement);
964     if( keys(%$res) == 1) {
965         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
966         if ($act_status eq "down" && $new_status eq "down") {
967             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968             $res = $known_server_db->del_dbentry($sql_statement);
969             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970         } 
971         else { 
972             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973             $res = $known_server_db->update_dbentry($sql_statement);
974             if($new_status eq "down"){
975                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976             } else {
977                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978             }
979         }
980     }
981     return $error; 
985 sub update_jobdb_status_for_send_msgs {
986     my ($answer, $error) = @_;
987     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
988         my $jobdb_id = $1;
989             
990         # sending msg faild
991         if( $error ) {
992             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
993                 my $sql_statement = "UPDATE $job_queue_tn ".
994                     "SET status='error', result='can not deliver msg, please consult log file' ".
995                     "WHERE id=$jobdb_id";
996                 my $res = $job_db->update_dbentry($sql_statement);
997             }
999         # sending msg was successful
1000         } else {
1001             my $sql_statement = "UPDATE $job_queue_tn ".
1002                 "SET status='done' ".
1003                 "WHERE id=$jobdb_id AND status='processed'";
1004             my $res = $job_db->update_dbentry($sql_statement);
1005         }
1006     }
1010 sub sig_handler {
1011         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1012         daemon_log("0 INFO got signal '$signal'", 1); 
1013         $kernel->sig_handled();
1014         return;
1018 sub msg_to_decrypt {
1019     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1020     my $session_id = $session->ID;
1021     my ($msg, $msg_hash, $module);
1022     my $error = 0;
1024     # hole neue msg aus @msgs_to_decrypt
1025     my $next_msg = shift @msgs_to_decrypt;
1026     
1027     # entschlüssle sie
1029     # msg is from a new client or gosa
1030     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1031     # msg is from a gosa-si-server
1032     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1034     }
1035     # msg is from a gosa-si-client
1036     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1038     }
1039     # an error occurred
1040     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1042         # could not understand a msg from its server the client cause a re-registering process
1043         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1044                 "' to cause a re-registering of the client if necessary", 5);
1045         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1047         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1048             my $host_name = $hit->{'hostname'};
1049             my $host_key = $hit->{'hostkey'};
1050             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053         }
1054         $error++;
1055     }
1058     my $header;
1059     my $target;
1060     my $source;
1061     my $done = 0;
1062     my $sql;
1063     my $res;
1065     # check whether this message should be processed here
1066     if ($error == 0) {
1067         $header = @{$msg_hash->{'header'}}[0];
1068         $target = @{$msg_hash->{'target'}}[0];
1069         $source = @{$msg_hash->{'source'}}[0];
1070                 my $not_found_in_known_clients_db = 0;
1071                 my $not_found_in_known_server_db = 0;
1072                 my $not_found_in_foreign_clients_db = 0;
1073         my $local_address;
1074         my ($target_ip, $target_port) = split(':', $target);
1075                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1076                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1077                 } else {
1078             $local_address = $server_address;
1079         }
1081         # target and source is equal to GOSA -> process here
1082         if (not $done) {
1083             if ($target eq "GOSA" && $source eq "GOSA") {
1084                 $done = 1;                    
1085             }
1086         }
1088         # target is own address without forward_to_gosa-tag -> process here
1089         if (not $done) {
1090             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1091                 $done = 1;
1092                 if ($source eq "GOSA") {
1093                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1094                 }
1095                 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1096             }
1097         }
1099         # target is a client address in known_clients -> process here
1100                 if (not $done) {
1101                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1102                                 $res = $known_clients_db->select_dbentry($sql);
1103                                 if (keys(%$res) > 0) {
1104                                                 $done = 1; 
1105                                                 my $hostname = $res->{1}->{'hostname'};
1106                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1107                                                 #print STDERR "target is a client address in known_clients -> process here\n";
1108                                 } else {
1109                                                 $not_found_in_known_clients_db = 1;
1110                                 }
1111                 }
1112         
1113         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1114         if (not $done) {
1115             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1116             my $gosa_at;
1117             my $gosa_session_id;
1118             if (($target eq $local_address) && (defined $forward_to_gosa)){
1119                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1120                 if ($gosa_at ne $local_address) {
1121                     $done = 1;
1122                     #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1123                 }
1124             }
1125         }
1127         # if message should be processed here -> add message to incoming_db
1128                 if ($done) {
1129                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1130                                 # so gosa-si-server knows how to process this kind of messages
1131                                 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1132                                                 $module = "GosaPackages";
1133                                 }
1135                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1136                                                                 primkey=>[],
1137                                                                 headertag=>$header,
1138                                                                 targettag=>$target,
1139                                                                 xmlmessage=>&encode_base64($msg),
1140                                                                 timestamp=>&get_time,
1141                                                                 module=>$module,
1142                                                                 sessionid=>$session_id,
1143                                                                 } );
1144                 }
1146         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1147         if (not $done) {
1148             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1149             my $gosa_at;
1150             my $gosa_session_id;
1151             if (($target eq $local_address) && (defined $forward_to_gosa)){
1152                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1153                 if ($gosa_at eq $local_address) {
1154                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1155                     if( defined $session_reference ) {
1156                         $heap = $session_reference->get_heap();
1157                     }
1158                     if(exists $heap->{'client'}) {
1159                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1160                         $heap->{'client'}->put($msg);
1161                     }
1162                     $done = 1;
1163                     #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1164                 }
1165             }
1167         }
1169         # target is a client address in foreign_clients -> forward to registration server
1170         if (not $done) {
1171             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1172             $res = $foreign_clients_db->select_dbentry($sql);
1173             if (keys(%$res) > 0) {
1174                 my $hostname = $res->{1}->{'hostname'};
1175                 my $regserver = $res->{1}->{'regserver'};
1176                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1177                 my $res = $known_server_db->select_dbentry($sql);
1178                 if (keys(%$res) > 0) {
1179                     my $regserver_key = $res->{1}->{'hostkey'};
1180                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1181                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1182                     if ($source eq "GOSA") {
1183                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1184                     }
1185                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1186                 }
1187                 $done = 1;
1188                 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1189             } else {
1190                                 $not_found_in_foreign_clients_db = 1;
1191                         }
1192         }
1194         # target is a server address -> forward to server
1195         if (not $done) {
1196             $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1197             $res = $known_server_db->select_dbentry($sql);
1198             if (keys(%$res) > 0) {
1199                 my $hostkey = $res->{1}->{'hostkey'};
1201                 if ($source eq "GOSA") {
1202                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1203                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1205                 }
1207                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1208                 $done = 1;
1209                 #print STDERR "target is a server address -> forward to server\n";
1210             } else {
1211                                 $not_found_in_known_server_db = 1;
1212                         }
1213         }
1215                 
1216                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1217                 if ( $not_found_in_foreign_clients_db 
1218                                                 && $not_found_in_known_server_db
1219                                                 && $not_found_in_known_clients_db) {
1220                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1221                                                                 primkey=>[],
1222                                                                 headertag=>$header,
1223                                                                 targettag=>$target,
1224                                                                 xmlmessage=>&encode_base64($msg),
1225                                                                 timestamp=>&get_time,
1226                                                                 module=>$module,
1227                                                                 sessionid=>$session_id,
1228                                                                 } );
1229                                 $done = 1;
1230                 }
1233         if (not $done) {
1234             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1235             if ($source eq "GOSA") {
1236                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1237                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1239                 my $session_reference = $kernel->ID_id_to_session($session_id);
1240                 if( defined $session_reference ) {
1241                     $heap = $session_reference->get_heap();
1242                 }
1243                 if(exists $heap->{'client'}) {
1244                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1245                     $heap->{'client'}->put($error_msg);
1246                 }
1247             }
1248         }
1250     }
1252     return;
1256 sub next_task {
1257     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1258     my $running_task = POE::Wheel::Run->new(
1259             Program => sub { process_task($session, $heap, $task) },
1260             StdioFilter => POE::Filter::Reference->new(),
1261             StdoutEvent  => "task_result",
1262             StderrEvent  => "task_debug",
1263             CloseEvent   => "task_done",
1264             );
1265     $heap->{task}->{ $running_task->ID } = $running_task;
1268 sub handle_task_result {
1269     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1270     my $client_answer = $result->{'answer'};
1271     if( $client_answer =~ s/session_id=(\d+)$// ) {
1272         my $session_id = $1;
1273         if( defined $session_id ) {
1274             my $session_reference = $kernel->ID_id_to_session($session_id);
1275             if( defined $session_reference ) {
1276                 $heap = $session_reference->get_heap();
1277             }
1278         }
1280         if(exists $heap->{'client'}) {
1281             $heap->{'client'}->put($client_answer);
1282         }
1283     }
1284     $kernel->sig(CHLD => "child_reap");
1287 sub handle_task_debug {
1288     my $result = $_[ARG0];
1289     print STDERR "$result\n";
1292 sub handle_task_done {
1293     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1294     delete $heap->{task}->{$task_id};
1297 sub process_task {
1298     no strict "refs";
1299     my ($session, $heap, $task) = @_;
1300     my $error = 0;
1301     my $answer_l;
1302     my ($answer_header, @answer_target_l, $answer_source);
1303     my $client_answer = "";
1305     # prepare all variables needed to process message
1306     #my $msg = $task->{'xmlmessage'};
1307     my $msg = &decode_base64($task->{'xmlmessage'});
1308     my $incoming_id = $task->{'id'};
1309     my $module = $task->{'module'};
1310     my $header =  $task->{'headertag'};
1311     my $session_id = $task->{'sessionid'};
1312     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1313     my $source = @{$msg_hash->{'source'}}[0];
1314     
1315     # set timestamp of incoming client uptodate, so client will not 
1316     # be deleted from known_clients because of expiration
1317     my $act_time = &get_time();
1318     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1319     my $res = $known_clients_db->exec_statement($sql);
1321     ######################
1322     # process incoming msg
1323     if( $error == 0) {
1324         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1325         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1326         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1328         if ( 0 < @{$answer_l} ) {
1329             my $answer_str = join("\n", @{$answer_l});
1330             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1331                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1332             }
1333             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1334         } else {
1335             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1336         }
1338     }
1339     if( !$answer_l ) { $error++ };
1341     ########
1342     # answer
1343     if( $error == 0 ) {
1345         foreach my $answer ( @{$answer_l} ) {
1346             # check outgoing msg to xml validity
1347             my $answer_hash = &check_outgoing_xml_validity($answer);
1348             if( not defined $answer_hash ) { next; }
1349             
1350             $answer_header = @{$answer_hash->{'header'}}[0];
1351             @answer_target_l = @{$answer_hash->{'target'}};
1352             $answer_source = @{$answer_hash->{'source'}}[0];
1354             # deliver msg to all targets 
1355             foreach my $answer_target ( @answer_target_l ) {
1357                 # targets of msg are all gosa-si-clients in known_clients_db
1358                 if( $answer_target eq "*" ) {
1359                     # answer is for all clients
1360                     my $sql_statement= "SELECT * FROM known_clients";
1361                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1362                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1363                         my $host_name = $hit->{hostname};
1364                         my $host_key = $hit->{hostkey};
1365                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1366                         &update_jobdb_status_for_send_msgs($answer, $error);
1367                     }
1368                 }
1370                 # targets of msg are all gosa-si-server in known_server_db
1371                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1372                     # answer is for all server in known_server
1373                     my $sql_statement= "SELECT * FROM $known_server_tn";
1374                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1375                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1376                         my $host_name = $hit->{hostname};
1377                         my $host_key = $hit->{hostkey};
1378                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1379                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1380                         &update_jobdb_status_for_send_msgs($answer, $error);
1381                     }
1382                 }
1384                 # target of msg is GOsa
1385                                 elsif( $answer_target eq "GOSA" ) {
1386                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1387                                         my $add_on = "";
1388                     if( defined $session_id ) {
1389                         $add_on = ".session_id=$session_id";
1390                     }
1391                     # answer is for GOSA and has to returned to connected client
1392                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1393                     $client_answer = $gosa_answer.$add_on;
1394                 }
1396                 # target of msg is job queue at this host
1397                 elsif( $answer_target eq "JOBDB") {
1398                     $answer =~ /<header>(\S+)<\/header>/;   
1399                     my $header;
1400                     if( defined $1 ) { $header = $1; }
1401                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1402                     &update_jobdb_status_for_send_msgs($answer, $error);
1403                 }
1405                 # target of msg is a mac address
1406                 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 ) {
1407                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1408                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1409                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1410                     my $found_ip_flag = 0;
1411                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1412                         my $host_name = $hit->{hostname};
1413                         my $host_key = $hit->{hostkey};
1414                         $answer =~ s/$answer_target/$host_name/g;
1415                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1416                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1417                         &update_jobdb_status_for_send_msgs($answer, $error);
1418                         $found_ip_flag++ ;
1419                     }   
1420                     if( $found_ip_flag == 0) {
1421                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1422                     }
1424                 #  answer is for one specific host   
1425                 } else {
1426                     # get encrypt_key
1427                     my $encrypt_key = &get_encrypt_key($answer_target);
1428                     if( not defined $encrypt_key ) {
1429                         # unknown target
1430                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1431                         next;
1432                     }
1433                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1434                     &update_jobdb_status_for_send_msgs($answer, $error);
1435                 }
1436             }
1437         }
1438     }
1440     my $filter = POE::Filter::Reference->new();
1441     my %result = ( 
1442             status => "seems ok to me",
1443             answer => $client_answer,
1444             );
1446     my $output = $filter->put( [ \%result ] );
1447     print @$output;
1452 sub session_start {
1453     my ($kernel) = $_[KERNEL];
1454     $global_kernel = $kernel;
1455     $kernel->yield('register_at_foreign_servers');
1456         $kernel->yield('create_fai_server_db', $fai_server_tn );
1457         $kernel->yield('create_fai_release_db', $fai_release_tn );
1458     $kernel->yield('watch_for_next_tasks');
1459         $kernel->sig(USR1 => "sig_handler");
1460         $kernel->sig(USR2 => "create_packages_list_db");
1461         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1462         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1463         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1464     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1465         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1466     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1471 sub watch_for_done_jobs {
1472     my ($kernel,$heap) = @_[KERNEL, HEAP];
1474     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1475         my $res = $job_db->select_dbentry( $sql_statement );
1477     while( my ($id, $hit) = each %{$res} ) {
1478         my $jobdb_id = $hit->{id};
1479         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1480         my $res = $job_db->del_dbentry($sql_statement); 
1481     }
1483     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1487 sub watch_for_new_jobs {
1488         if($watch_for_new_jobs_in_progress == 0) {
1489                 $watch_for_new_jobs_in_progress = 1;
1490                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1492                 # check gosa job queue for jobs with executable timestamp
1493                 my $timestamp = &get_time();
1494                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1495                 my $res = $job_db->exec_statement( $sql_statement );
1497                 # Merge all new jobs that would do the same actions
1498                 my @drops;
1499                 my $hits;
1500                 foreach my $hit (reverse @{$res} ) {
1501                         my $macaddress= lc @{$hit}[8];
1502                         my $headertag= @{$hit}[5];
1503                         if(
1504                                 defined($hits->{$macaddress}) &&
1505                                 defined($hits->{$macaddress}->{$headertag}) &&
1506                                 defined($hits->{$macaddress}->{$headertag}[0])
1507                         ) {
1508                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1509                         }
1510                         $hits->{$macaddress}->{$headertag}= $hit;
1511                 }
1513                 # Delete new jobs with a matching job in state 'processing'
1514                 foreach my $macaddress (keys %{$hits}) {
1515                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1516                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1517                                 if(defined($jobdb_id)) {
1518                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1519                                         my $res = $job_db->exec_statement( $sql_statement );
1520                                         foreach my $hit (@{$res}) {
1521                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1522                                         }
1523                                 } else {
1524                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1525                                 }
1526                         }
1527                 }
1529                 # Commit deletion
1530                 $job_db->exec_statementlist(\@drops);
1532                 # Look for new jobs that could be executed
1533                 foreach my $macaddress (keys %{$hits}) {
1535                         # Look if there is an executing job
1536                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1537                         my $res = $job_db->exec_statement( $sql_statement );
1539                         # Skip new jobs for host if there is a processing job
1540                         if(defined($res) and defined @{$res}[0]) {
1541                                 next;
1542                         }
1544                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1545                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1546                                 if(defined($jobdb_id)) {
1547                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1549                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1550                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1551                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1553                                         # expect macaddress is unique!!!!!!
1554                                         my $target = $res_hash->{1}->{hostname};
1556                                         # change header
1557                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1559                                         # add sqlite_id
1560                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1562                                         $job_msg =~ /<header>(\S+)<\/header>/;
1563                                         my $header = $1 ;
1564                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1566                                         # update status in job queue to 'processing'
1567                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1568                                         my $res = $job_db->update_dbentry($sql_statement);
1569 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1571                                         # We don't want parallel processing
1572                                         last;
1573                                 }
1574                         }
1575                 }
1577                 $watch_for_new_jobs_in_progress = 0;
1578                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1579         }
1583 sub watch_for_new_messages {
1584     my ($kernel,$heap) = @_[KERNEL, HEAP];
1585     my @coll_user_msg;   # collection list of outgoing messages
1586     
1587     # check messaging_db for new incoming messages with executable timestamp
1588     my $timestamp = &get_time();
1589     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1590     my $res = $messaging_db->exec_statement( $sql_statement );
1591         foreach my $hit (@{$res}) {
1593         # create outgoing messages
1594         my $message_to = @{$hit}[3];
1595         # translate message_to to plain login name
1596         my @message_to_l = split(/,/, $message_to);  
1597                 my %receiver_h; 
1598                 foreach my $receiver (@message_to_l) {
1599                         if ($receiver =~ /^u_([\s\S]*)$/) {
1600                                 $receiver_h{$1} = 0;
1601                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1602                                 my $group_name = $1;
1603                                 # fetch all group members from ldap and add them to receiver hash
1604                                 my $ldap_handle = &get_ldap_handle();
1605                                 if (defined $ldap_handle) {
1606                                                 my $mesg = $ldap_handle->search(
1607                                                                                 base => $ldap_base,
1608                                                                                 scope => 'sub',
1609                                                                                 attrs => ['memberUid'],
1610                                                                                 filter => "cn=$group_name",
1611                                                                                 );
1612                                                 if ($mesg->count) {
1613                                                                 my @entries = $mesg->entries;
1614                                                                 foreach my $entry (@entries) {
1615                                                                                 my @receivers= $entry->get_value("memberUid");
1616                                                                                 foreach my $receiver (@receivers) { 
1617                                                                                                 $receiver_h{$1} = 0;
1618                                                                                 }
1619                                                                 }
1620                                                 } 
1621                                                 # translating errors ?
1622                                                 if ($mesg->code) {
1623                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1624                                                 }
1625                                 # ldap handle error ?           
1626                                 } else {
1627                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1628                                 }
1629                         } else {
1630                                 my $sbjct = &encode_base64(@{$hit}[1]);
1631                                 my $msg = &encode_base64(@{$hit}[7]);
1632                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1633                         }
1634                 }
1635                 my @receiver_l = keys(%receiver_h);
1637         my $message_id = @{$hit}[0];
1639         #add each outgoing msg to messaging_db
1640         my $receiver;
1641         foreach $receiver (@receiver_l) {
1642             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1643                 "VALUES ('".
1644                 $message_id."', '".    # id
1645                 @{$hit}[1]."', '".     # subject
1646                 @{$hit}[2]."', '".     # message_from
1647                 $receiver."', '".      # message_to
1648                 "none"."', '".         # flag
1649                 "out"."', '".          # direction
1650                 @{$hit}[6]."', '".     # delivery_time
1651                 @{$hit}[7]."', '".     # message
1652                 $timestamp."'".     # timestamp
1653                 ")";
1654             &daemon_log("M DEBUG: $sql_statement", 1);
1655             my $res = $messaging_db->exec_statement($sql_statement);
1656             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1657         }
1659         # set incoming message to flag d=deliverd
1660         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1661         &daemon_log("M DEBUG: $sql_statement", 7);
1662         $res = $messaging_db->update_dbentry($sql_statement);
1663         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1664     }
1666     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1667     return;
1670 sub watch_for_delivery_messages {
1671     my ($kernel, $heap) = @_[KERNEL, HEAP];
1673     # select outgoing messages
1674     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1675     #&daemon_log("0 DEBUG: $sql", 7);
1676     my $res = $messaging_db->exec_statement( $sql_statement );
1677     
1678     # build out msg for each    usr
1679     foreach my $hit (@{$res}) {
1680         my $receiver = @{$hit}[3];
1681         my $msg_id = @{$hit}[0];
1682         my $subject = @{$hit}[1];
1683         my $message = @{$hit}[7];
1685         # resolve usr -> host where usr is logged in
1686         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1687         #&daemon_log("0 DEBUG: $sql", 7);
1688         my $res = $login_users_db->exec_statement($sql);
1690         # reciver is logged in nowhere
1691         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1693                 my $send_succeed = 0;
1694                 foreach my $hit (@$res) {
1695                                 my $receiver_host = @$hit[0];
1696                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1698                                 # fetch key to encrypt msg propperly for usr/host
1699                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1700                                 &daemon_log("0 DEBUG: $sql", 7);
1701                                 my $res = $known_clients_db->exec_statement($sql);
1703                                 # host is already down
1704                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1706                                 # host is on
1707                                 my $receiver_key = @{@{$res}[0]}[2];
1708                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1709                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1710                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1711                                 if ($error == 0 ) {
1712                                         $send_succeed++ ;
1713                                 }
1714                 }
1716                 if ($send_succeed) {
1717                                 # set outgoing msg at db to deliverd
1718                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1719                                 &daemon_log("0 DEBUG: $sql", 7);
1720                                 my $res = $messaging_db->exec_statement($sql); 
1721                 }
1722         }
1724     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1725     return;
1729 sub watch_for_done_messages {
1730     my ($kernel,$heap) = @_[KERNEL, HEAP];
1732     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1733     #&daemon_log("0 DEBUG: $sql", 7);
1734     my $res = $messaging_db->exec_statement($sql); 
1736     foreach my $hit (@{$res}) {
1737         my $msg_id = @{$hit}[0];
1739         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1740         #&daemon_log("0 DEBUG: $sql", 7); 
1741         my $res = $messaging_db->exec_statement($sql);
1743         # not all usr msgs have been seen till now
1744         if ( ref(@$res[0]) eq "ARRAY") { next; }
1745         
1746         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1747         #&daemon_log("0 DEBUG: $sql", 7);
1748         $res = $messaging_db->exec_statement($sql);
1749     
1750     }
1752     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1753     return;
1757 sub watch_for_old_known_clients {
1758     my ($kernel,$heap) = @_[KERNEL, HEAP];
1760     my $sql_statement = "SELECT * FROM $known_clients_tn";
1761     my $res = $known_clients_db->select_dbentry( $sql_statement );
1763     my $act_time = int(&get_time());
1765     while ( my ($hit_num, $hit) = each %$res) {
1766         my $expired_timestamp = int($hit->{'timestamp'});
1767         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1768         my $dt = DateTime->new( year   => $1,
1769                 month  => $2,
1770                 day    => $3,
1771                 hour   => $4,
1772                 minute => $5,
1773                 second => $6,
1774                 );
1776         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1777         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1778         if ($act_time > $expired_timestamp) {
1779             my $hostname = $hit->{'hostname'};
1780             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1781             my $del_res = $known_clients_db->exec_statement($del_sql);
1783             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1784         }
1786     }
1788     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1792 sub watch_for_next_tasks {
1793     my ($kernel,$heap) = @_[KERNEL, HEAP];
1795     my $sql = "SELECT * FROM $incoming_tn";
1796     my $res = $incoming_db->select_dbentry($sql);
1798     while ( my ($hit_num, $hit) = each %$res) {
1799         my $headertag = $hit->{'headertag'};
1800         if ($headertag =~ /^answer_(\d+)/) {
1801             # do not start processing, this message is for a still running POE::Wheel
1802             next;
1803         }
1804         my $message_id = $hit->{'id'};
1805         $kernel->yield('next_task', $hit);
1807         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1808         my $res = $incoming_db->exec_statement($sql);
1809     }
1811     $kernel->delay_set('watch_for_next_tasks', 0.1); 
1815 sub get_ldap_handle {
1816         my ($session_id) = @_;
1817         my $heap;
1818         my $ldap_handle;
1820         if (not defined $session_id ) { $session_id = 0 };
1821         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1823         if ($session_id == 0) {
1824                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1825                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1826                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
1828         } else {
1829                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1830                 if( defined $session_reference ) {
1831                         $heap = $session_reference->get_heap();
1832                 }
1834                 if (not defined $heap) {
1835                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1836                         return;
1837                 }
1839                 # TODO: This "if" is nonsense, because it doesn't prove that the
1840                 #       used handle is still valid - or if we've to reconnect...
1841                 #if (not exists $heap->{ldap_handle}) {
1842                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1843                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
1844                         $heap->{ldap_handle} = $ldap_handle;
1845                 #}
1846         }
1847         return $ldap_handle;
1851 sub change_fai_state {
1852     my ($st, $targets, $session_id) = @_;
1853     $session_id = 0 if not defined $session_id;
1854     # Set FAI state to localboot
1855     my %mapActions= (
1856         reboot    => '',
1857         update    => 'softupdate',
1858         localboot => 'localboot',
1859         reinstall => 'install',
1860         rescan    => '',
1861         wake      => '',
1862         memcheck  => 'memcheck',
1863         sysinfo   => 'sysinfo',
1864         install   => 'install',
1865     );
1867     # Return if this is unknown
1868     if (!exists $mapActions{ $st }){
1869         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1870       return;
1871     }
1873     my $state= $mapActions{ $st };
1875     my $ldap_handle = &get_ldap_handle($session_id);
1876     if( defined($ldap_handle) ) {
1878       # Build search filter for hosts
1879         my $search= "(&(objectClass=GOhard)";
1880         foreach (@{$targets}){
1881             $search.= "(macAddress=$_)";
1882         }
1883         $search.= ")";
1885       # If there's any host inside of the search string, procress them
1886         if (!($search =~ /macAddress/)){
1887             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1888             return;
1889         }
1891       # Perform search for Unit Tag
1892       my $mesg = $ldap_handle->search(
1893           base   => $ldap_base,
1894           scope  => 'sub',
1895           attrs  => ['dn', 'FAIstate', 'objectClass'],
1896           filter => "$search"
1897           );
1899           if ($mesg->count) {
1900                   my @entries = $mesg->entries;
1901                   if (0 == @entries) {
1902                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
1903                   }
1905                   foreach my $entry (@entries) {
1906                           # Only modify entry if it is not set to '$state'
1907                           if ($entry->get_value("FAIstate") ne "$state"){
1908                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1909                                   my $result;
1910                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1911                                   if (exists $tmp{'FAIobject'}){
1912                                           if ($state eq ''){
1913                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1914                                                           delete => [ FAIstate => [] ] ]);
1915                                           } else {
1916                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1917                                                           replace => [ FAIstate => $state ] ]);
1918                                           }
1919                                   } elsif ($state ne ''){
1920                                           $result= $ldap_handle->modify($entry->dn, changes => [
1921                                                   add     => [ objectClass => 'FAIobject' ],
1922                                                   add     => [ FAIstate => $state ] ]);
1923                                   }
1925                                   # Errors?
1926                                   if ($result->code){
1927                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1928                                   }
1929                           } else {
1930                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1931                           }  
1932                   }
1933           } else {
1934                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1935           }
1937     # if no ldap handle defined
1938     } else {
1939         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1940     }
1942         return;
1946 sub change_goto_state {
1947     my ($st, $targets, $session_id) = @_;
1948     $session_id = 0  if not defined $session_id;
1950     # Switch on or off?
1951     my $state= $st eq 'active' ? 'active': 'locked';
1953     my $ldap_handle = &get_ldap_handle($session_id);
1954     if( defined($ldap_handle) ) {
1956       # Build search filter for hosts
1957       my $search= "(&(objectClass=GOhard)";
1958       foreach (@{$targets}){
1959         $search.= "(macAddress=$_)";
1960       }
1961       $search.= ")";
1963       # If there's any host inside of the search string, procress them
1964       if (!($search =~ /macAddress/)){
1965         return;
1966       }
1968       # Perform search for Unit Tag
1969       my $mesg = $ldap_handle->search(
1970           base   => $ldap_base,
1971           scope  => 'sub',
1972           attrs  => ['dn', 'gotoMode'],
1973           filter => "$search"
1974           );
1976       if ($mesg->count) {
1977         my @entries = $mesg->entries;
1978         foreach my $entry (@entries) {
1980           # Only modify entry if it is not set to '$state'
1981           if ($entry->get_value("gotoMode") ne $state){
1983             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1984             my $result;
1985             $result= $ldap_handle->modify($entry->dn, changes => [
1986                                                 replace => [ gotoMode => $state ] ]);
1988             # Errors?
1989             if ($result->code){
1990               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1991             }
1993           }
1994         }
1995       } else {
1996                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
1997           }
1999     }
2003 sub run_create_fai_server_db {
2004     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2005     my $session_id = $session->ID;
2006     my $task = POE::Wheel::Run->new(
2007             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2008             StdoutEvent  => "session_run_result",
2009             StderrEvent  => "session_run_debug",
2010             CloseEvent   => "session_run_done",
2011             );
2013     $heap->{task}->{ $task->ID } = $task;
2014     return;
2018 sub create_fai_server_db {
2019     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2020         my $result;
2022         if (not defined $session_id) { $session_id = 0; }
2023     my $ldap_handle = &get_ldap_handle();
2024         if(defined($ldap_handle)) {
2025                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2026                 my $mesg= $ldap_handle->search(
2027                         base   => $ldap_base,
2028                         scope  => 'sub',
2029                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2030                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2031                 );
2032                 if($mesg->{'resultCode'} == 0 &&
2033                    $mesg->count != 0) {
2034                    foreach my $entry (@{$mesg->{entries}}) {
2035                            if($entry->exists('FAIrepository')) {
2036                                    # Add an entry for each Repository configured for server
2037                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2038                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2039                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2040                                                    $result= $fai_server_db->add_dbentry( { 
2041                                                                    table => $table_name,
2042                                                                    primkey => ['server', 'release', 'tag'],
2043                                                                    server => $tmp_url,
2044                                                                    release => $tmp_release,
2045                                                                    sections => $tmp_sections,
2046                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2047                                                            } );
2048                                            }
2049                                    }
2050                            }
2051                    }
2052                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2054                 # TODO: Find a way to post the 'create_packages_list_db' event
2055                 if(not defined($dont_create_packages_list)) {
2056                         &create_packages_list_db(undef, undef, $session_id);
2057                 }
2058         }       
2059     
2060     $ldap_handle->disconnect;
2061         return $result;
2065 sub run_create_fai_release_db {
2066     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2067         my $session_id = $session->ID;
2068     my $task = POE::Wheel::Run->new(
2069             Program => sub { &create_fai_release_db($table_name, $session_id) },
2070             StdoutEvent  => "session_run_result",
2071             StderrEvent  => "session_run_debug",
2072             CloseEvent   => "session_run_done",
2073             );
2075     $heap->{task}->{ $task->ID } = $task;
2076     return;
2080 sub create_fai_release_db {
2081         my ($table_name, $session_id) = @_;
2082         my $result;
2084     # used for logging
2085     if (not defined $session_id) { $session_id = 0; }
2087     my $ldap_handle = &get_ldap_handle();
2088         if(defined($ldap_handle)) {
2089                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2090                 my $mesg= $ldap_handle->search(
2091                         base   => $ldap_base,
2092                         scope  => 'sub',
2093                         attrs  => [],
2094                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2095                 );
2096                 if($mesg->{'resultCode'} == 0 &&
2097                         $mesg->count != 0) {
2098                         # Walk through all possible FAI container ou's
2099                         my @sql_list;
2100                         my $timestamp= &get_time();
2101                         foreach my $ou (@{$mesg->{entries}}) {
2102                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2103                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2104                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2105                                         if(@tmp_array) {
2106                                                 foreach my $entry (@tmp_array) {
2107                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2108                                                                 my $sql= 
2109                                                                 "INSERT INTO $table_name "
2110                                                                 ."(timestamp, release, class, type, state) VALUES ("
2111                                                                 .$timestamp.","
2112                                                                 ."'".$entry->{'release'}."',"
2113                                                                 ."'".$entry->{'class'}."',"
2114                                                                 ."'".$entry->{'type'}."',"
2115                                                                 ."'".$entry->{'state'}."')";
2116                                                                 push @sql_list, $sql;
2117                                                         }
2118                                                 }
2119                                         }
2120                                 }
2121                         }
2123                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2124                         if(@sql_list) {
2125                                 unshift @sql_list, "VACUUM";
2126                                 unshift @sql_list, "DELETE FROM $table_name";
2127                                 $fai_release_db->exec_statementlist(\@sql_list);
2128                         }
2129                         daemon_log("$session_id DEBUG: Done with inserting",7);
2130                 }
2131                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2132         }
2133     $ldap_handle->disconnect;
2134         return $result;
2137 sub get_fai_types {
2138         my $tmp_classes = shift || return undef;
2139         my @result;
2141         foreach my $type(keys %{$tmp_classes}) {
2142                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2143                         my $entry = {
2144                                 type => $type,
2145                                 state => $tmp_classes->{$type}[0],
2146                         };
2147                         push @result, $entry;
2148                 }
2149         }
2151         return @result;
2154 sub get_fai_state {
2155         my $result = "";
2156         my $tmp_classes = shift || return $result;
2158         foreach my $type(keys %{$tmp_classes}) {
2159                 if(defined($tmp_classes->{$type}[0])) {
2160                         $result = $tmp_classes->{$type}[0];
2161                         
2162                 # State is equal for all types in class
2163                         last;
2164                 }
2165         }
2167         return $result;
2170 sub resolve_fai_classes {
2171         my ($fai_base, $ldap_handle, $session_id) = @_;
2172         if (not defined $session_id) { $session_id = 0; }
2173         my $result;
2174         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2175         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2176         my $fai_classes;
2178         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2179         my $mesg= $ldap_handle->search(
2180                 base   => $fai_base,
2181                 scope  => 'sub',
2182                 attrs  => ['cn','objectClass','FAIstate'],
2183                 filter => $fai_filter,
2184         );
2185         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2187         if($mesg->{'resultCode'} == 0 &&
2188                 $mesg->count != 0) {
2189                 foreach my $entry (@{$mesg->{entries}}) {
2190                         if($entry->exists('cn')) {
2191                                 my $tmp_dn= $entry->dn();
2193                                 # Skip classname and ou dn parts for class
2194                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2196                                 # Skip classes without releases
2197                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2198                                         next;
2199                                 }
2201                                 my $tmp_cn= $entry->get_value('cn');
2202                                 my $tmp_state= $entry->get_value('FAIstate');
2204                                 my $tmp_type;
2205                                 # Get FAI type
2206                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2207                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2208                                                 $tmp_type= $oclass;
2209                                                 last;
2210                                         }
2211                                 }
2213                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2214                                         # A Subrelease
2215                                         my @sub_releases = split(/,/, $tmp_release);
2217                                         # Walk through subreleases and build hash tree
2218                                         my $hash;
2219                                         while(my $tmp_sub_release = pop @sub_releases) {
2220                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2221                                         }
2222                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2223                                 } else {
2224                                         # A branch, no subrelease
2225                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2226                                 }
2227                         } elsif (!$entry->exists('cn')) {
2228                                 my $tmp_dn= $entry->dn();
2229                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2231                                 # Skip classes without releases
2232                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2233                                         next;
2234                                 }
2236                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2237                                         # A Subrelease
2238                                         my @sub_releases= split(/,/, $tmp_release);
2240                                         # Walk through subreleases and build hash tree
2241                                         my $hash;
2242                                         while(my $tmp_sub_release = pop @sub_releases) {
2243                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2244                                         }
2245                                         # Remove the last two characters
2246                                         chop($hash);
2247                                         chop($hash);
2249                                         eval('$fai_classes->'.$hash.'= {}');
2250                                 } else {
2251                                         # A branch, no subrelease
2252                                         if(!exists($fai_classes->{$tmp_release})) {
2253                                                 $fai_classes->{$tmp_release} = {};
2254                                         }
2255                                 }
2256                         }
2257                 }
2259                 # The hash is complete, now we can honor the copy-on-write based missing entries
2260                 foreach my $release (keys %$fai_classes) {
2261                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2262                 }
2263         }
2264         return $result;
2267 sub apply_fai_inheritance {
2268        my $fai_classes = shift || return {};
2269        my $tmp_classes;
2271        # Get the classes from the branch
2272        foreach my $class (keys %{$fai_classes}) {
2273                # Skip subreleases
2274                if($class =~ /^ou=.*$/) {
2275                        next;
2276                } else {
2277                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2278                }
2279        }
2281        # Apply to each subrelease
2282        foreach my $subrelease (keys %{$fai_classes}) {
2283                if($subrelease =~ /ou=/) {
2284                        foreach my $tmp_class (keys %{$tmp_classes}) {
2285                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2286                                        $fai_classes->{$subrelease}->{$tmp_class} =
2287                                        deep_copy($tmp_classes->{$tmp_class});
2288                                } else {
2289                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2290                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2291                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2292                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2293                                                }
2294                                        }
2295                                }
2296                        }
2297                }
2298        }
2300        # Find subreleases in deeper levels
2301        foreach my $subrelease (keys %{$fai_classes}) {
2302                if($subrelease =~ /ou=/) {
2303                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2304                                if($subsubrelease =~ /ou=/) {
2305                                        apply_fai_inheritance($fai_classes->{$subrelease});
2306                                }
2307                        }
2308                }
2309        }
2311        return $fai_classes;
2314 sub get_fai_release_entries {
2315         my $tmp_classes = shift || return;
2316         my $parent = shift || "";
2317         my @result = shift || ();
2319         foreach my $entry (keys %{$tmp_classes}) {
2320                 if(defined($entry)) {
2321                         if($entry =~ /^ou=.*$/) {
2322                                 my $release_name = $entry;
2323                                 $release_name =~ s/ou=//g;
2324                                 if(length($parent)>0) {
2325                                         $release_name = $parent."/".$release_name;
2326                                 }
2327                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2328                                 foreach my $bufentry(@bufentries) {
2329                                         push @result, $bufentry;
2330                                 }
2331                         } else {
2332                                 my @types = get_fai_types($tmp_classes->{$entry});
2333                                 foreach my $type (@types) {
2334                                         push @result, 
2335                                         {
2336                                                 'class' => $entry,
2337                                                 'type' => $type->{'type'},
2338                                                 'release' => $parent,
2339                                                 'state' => $type->{'state'},
2340                                         };
2341                                 }
2342                         }
2343                 }
2344         }
2346         return @result;
2349 sub deep_copy {
2350         my $this = shift;
2351         if (not ref $this) {
2352                 $this;
2353         } elsif (ref $this eq "ARRAY") {
2354                 [map deep_copy($_), @$this];
2355         } elsif (ref $this eq "HASH") {
2356                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2357         } else { die "what type is $_?" }
2361 sub session_run_result {
2362     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2363     $kernel->sig(CHLD => "child_reap");
2366 sub session_run_debug {
2367     my $result = $_[ARG0];
2368     print STDERR "$result\n";
2371 sub session_run_done {
2372     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2373     delete $heap->{task}->{$task_id};
2377 sub create_sources_list {
2378         my $session_id = shift;
2379         my $ldap_handle = &main::get_ldap_handle;
2380         my $result="/tmp/gosa_si_tmp_sources_list";
2382         # Remove old file
2383         if(stat($result)) {
2384                 unlink($result);
2385                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2386         }
2388         my $fh;
2389         open($fh, ">$result");
2390         if (not defined $fh) {
2391                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2392                 return undef;
2393         }
2394         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2395                 my $mesg=$ldap_handle->search(
2396                         base    => $main::ldap_server_dn,
2397                         scope   => 'base',
2398                         attrs   => 'FAIrepository',
2399                         filter  => 'objectClass=FAIrepositoryServer'
2400                 );
2401                 if($mesg->count) {
2402                         foreach my $entry(@{$mesg->{'entries'}}) {
2403                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2404                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2405                                         my $line = "deb $server $release";
2406                                         $sections =~ s/,/ /g;
2407                                         $line.= " $sections";
2408                                         print $fh $line."\n";
2409                                 }
2410                         }
2411                 }
2412         } else {
2413                 if (defined $main::ldap_server_dn){
2414                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2415                 } else {
2416                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2417                 }
2418         }
2419         close($fh);
2421         return $result;
2425 sub run_create_packages_list_db {
2426     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2427         my $session_id = $session->ID;
2429         my $task = POE::Wheel::Run->new(
2430                                         Priority => +20,
2431                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2432                                         StdoutEvent  => "session_run_result",
2433                                         StderrEvent  => "session_run_debug",
2434                                         CloseEvent   => "session_run_done",
2435                                         );
2436         $heap->{task}->{ $task->ID } = $task;
2440 sub create_packages_list_db {
2441         my ($ldap_handle, $sources_file, $session_id) = @_;
2442         
2443         # it should not be possible to trigger a recreation of packages_list_db
2444         # while packages_list_db is under construction, so set flag packages_list_under_construction
2445         # which is tested befor recreation can be started
2446         if (-r $packages_list_under_construction) {
2447                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2448                 return;
2449         } else {
2450                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2451                 # set packages_list_under_construction to true
2452                 system("touch $packages_list_under_construction");
2453                 @packages_list_statements=();
2454         }
2456         if (not defined $session_id) { $session_id = 0; }
2457         if (not defined $ldap_handle) { 
2458                 $ldap_handle= &get_ldap_handle();
2460                 if (not defined $ldap_handle) {
2461                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2462                         unlink($packages_list_under_construction);
2463                         return;
2464                 }
2465         }
2466         if (not defined $sources_file) { 
2467                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2468                 $sources_file = &create_sources_list($session_id);
2469         }
2471         if (not defined $sources_file) {
2472                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2473                 unlink($packages_list_under_construction);
2474                 return;
2475         }
2477         my $line;
2479         open(CONFIG, "<$sources_file") or do {
2480                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2481                 unlink($packages_list_under_construction);
2482                 return;
2483         };
2485         # Read lines
2486         while ($line = <CONFIG>){
2487                 # Unify
2488                 chop($line);
2489                 $line =~ s/^\s+//;
2490                 $line =~ s/^\s+/ /;
2492                 # Strip comments
2493                 $line =~ s/#.*$//g;
2495                 # Skip empty lines
2496                 if ($line =~ /^\s*$/){
2497                         next;
2498                 }
2500                 # Interpret deb line
2501                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2502                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2503                         my $section;
2504                         foreach $section (split(' ', $sections)){
2505                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2506                         }
2507                 }
2508         }
2510         close (CONFIG);
2512         find(\&cleanup_and_extract, keys( %repo_dirs ));
2513         &main::strip_packages_list_statements();
2514         unshift @packages_list_statements, "VACUUM";
2515         $packages_list_db->exec_statementlist(\@packages_list_statements);
2516         unlink($packages_list_under_construction);
2517         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2518         return;
2521 # This function should do some intensive task to minimize the db-traffic
2522 sub strip_packages_list_statements {
2523     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2524         my @new_statement_list=();
2525         my $hash;
2526         my $insert_hash;
2527         my $update_hash;
2528         my $delete_hash;
2529         my $local_timestamp=get_time();
2531         foreach my $existing_entry (@existing_entries) {
2532                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2533         }
2535         foreach my $statement (@packages_list_statements) {
2536                 if($statement =~ /^INSERT/i) {
2537                         # Assign the values from the insert statement
2538                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2539                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2540                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2541                                 # If section or description has changed, update the DB
2542                                 if( 
2543                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2544                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2545                                 ) {
2546                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2547                                 }
2548                         } else {
2549                                 # Insert a non-existing entry to db
2550                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2551                         }
2552                 } elsif ($statement =~ /^UPDATE/i) {
2553                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2554                         /^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;
2555                         foreach my $distribution (keys %{$hash}) {
2556                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2557                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2558                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2559                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2560                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2561                                                 my $section;
2562                                                 my $description;
2563                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2564                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2565                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2566                                                 }
2567                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2568                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2569                                                 }
2570                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2571                                         }
2572                                 }
2573                         }
2574                 }
2575         }
2577         # TODO: Check for orphaned entries
2579         # unroll the insert_hash
2580         foreach my $distribution (keys %{$insert_hash}) {
2581                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2582                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2583                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2584                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2585                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2586                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2587                                 ."'$local_timestamp')";
2588                         }
2589                 }
2590         }
2592         # unroll the update hash
2593         foreach my $distribution (keys %{$update_hash}) {
2594                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2595                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2596                                 my $set = "";
2597                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2598                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2599                                 }
2600                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2601                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2602                                 }
2603                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2604                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2605                                 }
2606                                 if(defined($set) and length($set) > 0) {
2607                                         $set .= "timestamp = '$local_timestamp'";
2608                                 } else {
2609                                         next;
2610                                 }
2611                                 push @new_statement_list, 
2612                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2613                                         ." distribution = '$distribution'"
2614                                         ." AND package = '$package'"
2615                                         ." AND version = '$version'";
2616                         }
2617                 }
2618         }
2620         @packages_list_statements = @new_statement_list;
2624 sub parse_package_info {
2625     my ($baseurl, $dist, $section, $session_id)= @_;
2626     my ($package);
2627     if (not defined $session_id) { $session_id = 0; }
2628     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2629     $repo_dirs{ "${repo_path}/pool" } = 1;
2631     foreach $package ("Packages.gz"){
2632         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2633         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2634         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2635     }
2636     
2640 sub get_package {
2641     my ($url, $dest, $session_id)= @_;
2642     if (not defined $session_id) { $session_id = 0; }
2644     my $tpath = dirname($dest);
2645     -d "$tpath" || mkpath "$tpath";
2647     # This is ugly, but I've no time to take a look at "how it works in perl"
2648     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2649         system("gunzip -cd '$dest' > '$dest.in'");
2650         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2651         unlink($dest);
2652         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2653     } else {
2654         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2655     }
2656     return 0;
2660 sub parse_package {
2661     my ($path, $dist, $srv_path, $session_id)= @_;
2662     if (not defined $session_id) { $session_id = 0;}
2663     my ($package, $version, $section, $description);
2664     my $PACKAGES;
2665     my $timestamp = &get_time();
2667     if(not stat("$path.in")) {
2668         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2669         return;
2670     }
2672     open($PACKAGES, "<$path.in");
2673     if(not defined($PACKAGES)) {
2674         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2675         return;
2676     }
2678     # Read lines
2679     while (<$PACKAGES>){
2680         my $line = $_;
2681         # Unify
2682         chop($line);
2684         # Use empty lines as a trigger
2685         if ($line =~ /^\s*$/){
2686             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2687             push(@packages_list_statements, $sql);
2688             $package = "none";
2689             $version = "none";
2690             $section = "none";
2691             $description = "none"; 
2692             next;
2693         }
2695         # Trigger for package name
2696         if ($line =~ /^Package:\s/){
2697             ($package)= ($line =~ /^Package: (.*)$/);
2698             next;
2699         }
2701         # Trigger for version
2702         if ($line =~ /^Version:\s/){
2703             ($version)= ($line =~ /^Version: (.*)$/);
2704             next;
2705         }
2707         # Trigger for description
2708         if ($line =~ /^Description:\s/){
2709             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2710             next;
2711         }
2713         # Trigger for section
2714         if ($line =~ /^Section:\s/){
2715             ($section)= ($line =~ /^Section: (.*)$/);
2716             next;
2717         }
2719         # Trigger for filename
2720         if ($line =~ /^Filename:\s/){
2721             my ($filename) = ($line =~ /^Filename: (.*)$/);
2722             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2723             next;
2724         }
2725     }
2727     close( $PACKAGES );
2728     unlink( "$path.in" );
2732 sub store_fileinfo {
2733     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2735     my %fileinfo = (
2736         'package' => $package,
2737         'dist' => $dist,
2738         'version' => $vers,
2739     );
2741     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2745 sub cleanup_and_extract {
2746     my $fileinfo = $repo_files{ $File::Find::name };
2748     if( defined $fileinfo ) {
2750         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2751         my $sql;
2752         my $package = $fileinfo->{ 'package' };
2753         my $newver = $fileinfo->{ 'version' };
2755         mkpath($dir);
2756         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2758                 if( -f "$dir/DEBIAN/templates" ) {
2760                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2762                         my $tmpl= "";
2763                         {
2764                                 local $/=undef;
2765                                 open FILE, "$dir/DEBIAN/templates";
2766                                 $tmpl = &encode_base64(<FILE>);
2767                                 close FILE;
2768                         }
2769                         rmtree("$dir/DEBIAN/templates");
2771                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2772                 push @packages_list_statements, $sql;
2773                 }
2774     }
2776     return;
2780 sub register_at_foreign_servers {   
2781     my ($kernel) = $_[KERNEL];
2783     # hole alle bekannten server aus known_server_db
2784     my $server_sql = "SELECT * FROM $known_server_tn";
2785     my $server_res = $known_server_db->exec_statement($server_sql);
2787     # no entries in known_server_db
2788     if (not ref(@$server_res[0]) eq "ARRAY") { 
2789         # TODO
2790     }
2792     # detect already connected clients
2793     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2794     my $client_res = $known_clients_db->exec_statement($client_sql);
2796     # send my server details to all other gosa-si-server within the network
2797     foreach my $hit (@$server_res) {
2798         my $hostname = @$hit[0];
2799         my $hostkey = &create_passwd;
2801         # add already connected clients to registration message 
2802         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2803         &add_content2xml_hash($myhash, 'key', $hostkey);
2804         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2805         
2806         # build registration message and send it
2807         my $foreign_server_msg = &create_xml_string($myhash);
2808         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2809     }
2810     
2811     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2812     return;
2816 #==== MAIN = main ==============================================================
2817 #  parse commandline options
2818 Getopt::Long::Configure( "bundling" );
2819 GetOptions("h|help" => \&usage,
2820         "c|config=s" => \$cfg_file,
2821         "f|foreground" => \$foreground,
2822         "v|verbose+" => \$verbose,
2823         "no-arp+" => \$no_arp,
2824            );
2826 #  read and set config parameters
2827 &check_cmdline_param ;
2828 &read_configfile;
2829 &check_pid;
2831 $SIG{CHLD} = 'IGNORE';
2833 # forward error messages to logfile
2834 if( ! $foreground ) {
2835   open( STDIN,  '+>/dev/null' );
2836   open( STDOUT, '+>&STDIN'    );
2837   open( STDERR, '+>&STDIN'    );
2840 # Just fork, if we are not in foreground mode
2841 if( ! $foreground ) { 
2842     chdir '/'                 or die "Can't chdir to /: $!";
2843     $pid = fork;
2844     setsid                    or die "Can't start a new session: $!";
2845     umask 0;
2846 } else { 
2847     $pid = $$; 
2850 # Do something useful - put our PID into the pid_file
2851 if( 0 != $pid ) {
2852     open( LOCK_FILE, ">$pid_file" );
2853     print LOCK_FILE "$pid\n";
2854     close( LOCK_FILE );
2855     if( !$foreground ) { 
2856         exit( 0 ) 
2857     };
2860 # parse head url and revision from svn
2861 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2862 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2863 $server_headURL = defined $1 ? $1 : 'unknown' ;
2864 $server_revision = defined $2 ? $2 : 'unknown' ;
2865 if ($server_headURL =~ /\/tag\// || 
2866         $server_headURL =~ /\/branches\// ) {
2867     $server_status = "stable"; 
2868 } else {
2869     $server_status = "developmental" ;
2873 daemon_log(" ", 1);
2874 daemon_log("$0 started!", 1);
2875 daemon_log("status: $server_status", 1);
2876 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2878 # connect to incoming_db
2879 unlink($incoming_file_name);
2880 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2881 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2883 # connect to gosa-si job queue
2884 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2885 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2887 # connect to known_clients_db
2888 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2889 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2891 # connect to foreign_clients_db
2892 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2893 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2895 # connect to known_server_db
2896 unlink($known_server_file_name);
2897 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2898 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2900 # connect to login_usr_db
2901 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2902 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2904 # connect to fai_server_db and fai_release_db
2905 unlink($fai_server_file_name);
2906 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2907 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2909 unlink($fai_release_file_name);
2910 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2911 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2913 # connect to packages_list_db
2914 #unlink($packages_list_file_name);
2915 unlink($packages_list_under_construction);
2916 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2917 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2919 # connect to messaging_db
2920 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2921 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2924 # create xml object used for en/decrypting
2925 $xml = new XML::Simple();
2928 # foreign servers 
2929 my @foreign_server_list;
2931 # add foreign server from cfg file
2932 if ($foreign_server_string ne "") {
2933     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2934     foreach my $foreign_server (@cfg_foreign_server_list) {
2935         push(@foreign_server_list, $foreign_server);
2936     }
2939 # add foreign server from dns
2940 my @tmp_servers;
2941 if ( !$server_domain) {
2942     # Try our DNS Searchlist
2943     for my $domain(get_dns_domains()) {
2944         chomp($domain);
2945         my @tmp_domains= &get_server_addresses($domain);
2946         if(@tmp_domains) {
2947             for my $tmp_server(@tmp_domains) {
2948                 push @tmp_servers, $tmp_server;
2949             }
2950         }
2951     }
2952     if(@tmp_servers && length(@tmp_servers)==0) {
2953         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2954     }
2955 } else {
2956     @tmp_servers = &get_server_addresses($server_domain);
2957     if( 0 == @tmp_servers ) {
2958         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2959     }
2961 foreach my $server (@tmp_servers) { 
2962     unshift(@foreign_server_list, $server); 
2964 # eliminate duplicate entries
2965 @foreign_server_list = &del_doubles(@foreign_server_list);
2966 my $all_foreign_server = join(", ", @foreign_server_list);
2967 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2969 # add all found foreign servers to known_server
2970 my $act_timestamp = &get_time();
2971 foreach my $foreign_server (@foreign_server_list) {
2973         #######################################
2974         # TODO for jan
2975         # do not add myself to known_server_db
2976         # work around!!!
2977         if ($foreign_server eq '172.16.2.89:20081') { next; }
2978         ######################################
2980     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2981             primkey=>['hostname'],
2982             hostname=>$foreign_server,
2983             status=>'not_jet_registered',
2984             hostkey=>"none",
2985             timestamp=>$act_timestamp,
2986             } );
2990 POE::Component::Server::TCP->new(
2991     Alias => "TCP_SERVER",
2992         Port => $server_port,
2993         ClientInput => sub {
2994         my ($kernel, $input) = @_[KERNEL, ARG0];
2995         push(@tasks, $input);
2996         push(@msgs_to_decrypt, $input);
2997         $kernel->yield("msg_to_decrypt");
2998         },
2999     InlineStates => {
3000         msg_to_decrypt => \&msg_to_decrypt,
3001         next_task => \&next_task,
3002         task_result => \&handle_task_result,
3003         task_done   => \&handle_task_done,
3004         task_debug  => \&handle_task_debug,
3005         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3006     }
3007 );
3009 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3011 # create session for repeatedly checking the job queue for jobs
3012 POE::Session->create(
3013         inline_states => {
3014                 _start => \&session_start,
3015         register_at_foreign_servers => \&register_at_foreign_servers,
3016         sig_handler => \&sig_handler,
3017         next_task => \&next_task,
3018         task_result => \&handle_task_result,
3019         task_done   => \&handle_task_done,
3020         task_debug  => \&handle_task_debug,
3021         watch_for_next_tasks => \&watch_for_next_tasks,
3022         watch_for_new_messages => \&watch_for_new_messages,
3023         watch_for_delivery_messages => \&watch_for_delivery_messages,
3024         watch_for_done_messages => \&watch_for_done_messages,
3025                 watch_for_new_jobs => \&watch_for_new_jobs,
3026         watch_for_done_jobs => \&watch_for_done_jobs,
3027         watch_for_old_known_clients => \&watch_for_old_known_clients,
3028         create_packages_list_db => \&run_create_packages_list_db,
3029         create_fai_server_db => \&run_create_fai_server_db,
3030         create_fai_release_db => \&run_create_fai_release_db,
3031         session_run_result => \&session_run_result,
3032         session_run_debug => \&session_run_debug,
3033         session_run_done => \&session_run_done,
3034         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3035         }
3036 );
3039 # import all modules
3040 &import_modules;
3042 # TODO
3043 # check wether all modules are gosa-si valid passwd check
3047 POE::Kernel->run();
3048 exit;