Code

8bc1100fd8952f69ad42795e76a594fea4ae8587
[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);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
81 # variables declared in config file are always set to 'our'
82 our (%cfg_defaults, $log_file, $pid_file, 
83     $server_ip, $server_port, $ClientPackages_key, 
84     $arp_activ, $gosa_unit_tag,
85     $GosaPackages_key, $gosa_timeout,
86     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
87     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
88     $arp_enabled, $arp_interface,
89     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $gosa_address;
96 our $no_arp;
97 our $verbose;
98 our $forground;
99 our $cfg_file;
100 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
101 our $known_modules;
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_arp = 0;
113 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
114 my @packages_list_statements;
115 my $watch_for_new_jobs_in_progress = 0;
117 # holds all incoming decrypted messages
118 our $incoming_db;
119 our $incoming_tn = 'incoming';
120 my $incoming_file_name;
121 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
122         "timestamp DEFAULT 'none'", 
123         "headertag DEFAULT 'none'",
124                 "targettag DEFAULT 'none'",
125         "xmlmessage DEFAULT 'none'",
126         "module DEFAULT 'none'",
127         "sessionid DEFAULT '0'",
128         );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
135                 "timestamp DEFAULT 'none'", 
136                 "status DEFAULT 'none'", 
137                 "result DEFAULT 'none'", 
138                 "progress DEFAULT 'none'", 
139         "headertag DEFAULT 'none'", 
140                 "targettag DEFAULT 'none'", 
141                 "xmlmessage DEFAULT 'none'", 
142                 "macaddress DEFAULT 'none'",
143                 "plainname DEFAULT 'none'",
144         "siserver DEFAULT 'none'",
145         "modified DEFAULT '0'",
146                 );
148 # holds all other gosa-si-server
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients"; 
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client 
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server"; 
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release"; 
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386"; 
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging"; 
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
195         "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai"; 
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 # loop delay for job queue to look for opsi jobs
208 my $job_queue_opsi_delay = 10;
209 our $opsi_client;
210 our $opsi_url;
211  
212 # Lifetime of logged in user information. If no update information comes after n seconds, 
213 # the user is expeceted to be no longer logged in or the host is no longer running. Because
214 # of this, the user is deleted from login_users_db
215 our $logged_in_user_date_of_expiry = 600;
218 %cfg_defaults = (
219 "general" => {
220     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
221     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
222     },
223 "server" => {
224     "ip" => [\$server_ip, "0.0.0.0"],
225     "port" => [\$server_port, "20081"],
226     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
227     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
228     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
229     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
230     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
231     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
232     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
233     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
234     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
235     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
236     "repo-path"            => [\$repo_path, '/srv/www/repository'],
237     "ldap-uri"             => [\$ldap_uri, ""],
238     "ldap-base"            => [\$ldap_base, ""],
239     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
240     "ldap-admin-password"  => [\$ldap_admin_password, ""],
241     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
242     "max-clients"          => [\$max_clients, 10],
243     "wol-password"           => [\$wake_on_lan_passwd, ""],
244     },
245 "GOsaPackages" => {
246     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
247     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
248     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
249     "key" => [\$GosaPackages_key, "none"],
250     },
251 "ClientPackages" => {
252     "key" => [\$ClientPackages_key, "none"],
253     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
254     },
255 "ServerPackages"=> {
256     "address"      => [\$foreign_server_string, ""],
257     "domain"  => [\$server_domain, ""],
258     "key"     => [\$ServerPackages_key, "none"],
259     "key-lifetime" => [\$foreign_servers_register_delay, 120],
260     "job-synchronization-enabled" => [\$job_synchronization, "true"],
261     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
262     },
263 "ArpHandler" => {
264     "enabled"   => [\$arp_enabled, "true"],
265     "interface" => [\$arp_interface, "all"],
266         },
267 "Opsi" => {
268     "enabled"  => [\$opsi_enabled, "false"], 
269     "server"   => [\$opsi_server, "localhost"],
270     "admin"    => [\$opsi_admin, "opsi-admin"],
271     "password" => [\$opsi_password, "secret"],
272    },
274 );
277 #===  FUNCTION  ================================================================
278 #         NAME:  usage
279 #   PARAMETERS:  nothing
280 #      RETURNS:  nothing
281 #  DESCRIPTION:  print out usage text to STDERR
282 #===============================================================================
283 sub usage {
284     print STDERR << "EOF" ;
285 usage: $prg [-hvf] [-c config]
287            -h        : this (help) message
288            -c <file> : config file
289            -f        : foreground, process will not be forked to background
290            -v        : be verbose (multiple to increase verbosity)
291            -no-arp   : starts $prg without connection to arp module
292  
293 EOF
294     print "\n" ;
298 #===  FUNCTION  ================================================================
299 #         NAME:  logging
300 #   PARAMETERS:  level - string - default 'info'
301 #                msg - string -
302 #                facility - string - default 'LOG_DAEMON'
303 #      RETURNS:  nothing
304 #  DESCRIPTION:  function for logging
305 #===============================================================================
306 sub daemon_log {
307     # log into log_file
308     my( $msg, $level ) = @_;
309     if(not defined $msg) { return }
310     if(not defined $level) { $level = 1 }
311     if(defined $log_file){
312         open(LOG_HANDLE, ">>$log_file");
313         chmod 0600, $log_file;
314         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
315             print STDERR "cannot open $log_file: $!";
316             return 
317         }
318         chomp($msg);
319         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
320         if($level <= $verbose){
321             my ($seconds, $minutes, $hours, $monthday, $month,
322                     $year, $weekday, $yearday, $sommertime) = localtime(time);
323             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
324             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
325             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
326             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
327             $month = $monthnames[$month];
328             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
329             $year+=1900;
330             my $name = $prg;
332             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
333             print LOG_HANDLE $log_msg;
334             if( $foreground ) { 
335                 print STDERR $log_msg;
336             }
337         }
338         close( LOG_HANDLE );
339     }
343 #===  FUNCTION  ================================================================
344 #         NAME:  check_cmdline_param
345 #   PARAMETERS:  nothing
346 #      RETURNS:  nothing
347 #  DESCRIPTION:  validates commandline parameter
348 #===============================================================================
349 sub check_cmdline_param () {
350     my $err_config;
351     my $err_counter = 0;
352         if(not defined($cfg_file)) {
353                 $cfg_file = "/etc/gosa-si/server.conf";
354                 if(! -r $cfg_file) {
355                         $err_config = "please specify a config file";
356                         $err_counter += 1;
357                 }
358     }
359     if( $err_counter > 0 ) {
360         &usage( "", 1 );
361         if( defined( $err_config)) { print STDERR "$err_config\n"}
362         print STDERR "\n";
363         exit( -1 );
364     }
368 #===  FUNCTION  ================================================================
369 #         NAME:  check_pid
370 #   PARAMETERS:  nothing
371 #      RETURNS:  nothing
372 #  DESCRIPTION:  handels pid processing
373 #===============================================================================
374 sub check_pid {
375     $pid = -1;
376     # Check, if we are already running
377     if( open(LOCK_FILE, "<$pid_file") ) {
378         $pid = <LOCK_FILE>;
379         if( defined $pid ) {
380             chomp( $pid );
381             if( -f "/proc/$pid/stat" ) {
382                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
383                 if( $stat ) {
384                                         daemon_log("ERROR: Already running",1);
385                     close( LOCK_FILE );
386                     exit -1;
387                 }
388             }
389         }
390         close( LOCK_FILE );
391         unlink( $pid_file );
392     }
394     # create a syslog msg if it is not to possible to open PID file
395     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
396         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
397         if (open(LOCK_FILE, '<', $pid_file)
398                 && ($pid = <LOCK_FILE>))
399         {
400             chomp($pid);
401             $msg .= "(PID $pid)\n";
402         } else {
403             $msg .= "(unable to read PID)\n";
404         }
405         if( ! ($foreground) ) {
406             openlog( $0, "cons,pid", "daemon" );
407             syslog( "warning", $msg );
408             closelog();
409         }
410         else {
411             print( STDERR " $msg " );
412         }
413         exit( -1 );
414     }
417 #===  FUNCTION  ================================================================
418 #         NAME:  import_modules
419 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
420 #                are stored
421 #      RETURNS:  nothing
422 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
423 #                state is on is imported by "require 'file';"
424 #===============================================================================
425 sub import_modules {
426     daemon_log(" ", 1);
428     if (not -e $modules_path) {
429         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
430     }
432     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
433     while (defined (my $file = readdir (DIR))) {
434         if (not $file =~ /(\S*?).pm$/) {
435             next;
436         }
437                 my $mod_name = $1;
439         # ArpHandler switch
440         if( $file =~ /ArpHandler.pm/ ) {
441             if( $arp_enabled eq "false" ) { next; }
442         }
443         
444         eval { require $file; };
445         if ($@) {
446             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
447             daemon_log("$@", 5);
448                 } else {
449                         my $info = eval($mod_name.'::get_module_info()');
450                         # Only load module if get_module_info() returns a non-null object
451                         if( $info ) {
452                                 my ($input_address, $input_key, $event_hash) = @{$info};
453                                 $known_modules->{$mod_name} = $info;
454                                 daemon_log("0 INFO: module $mod_name loaded", 5);
455                         }
456                 }
457     }   
459     close (DIR);
462 #===  FUNCTION  ================================================================
463 #         NAME:  password_check
464 #   PARAMETERS:  nothing
465 #      RETURNS:  nothing
466 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
467 #                the same password
468 #===============================================================================
469 sub password_check {
470     my $passwd_hash = {};
471     while (my ($mod_name, $mod_info) = each %$known_modules) {
472         my $mod_passwd = @$mod_info[1];
473         if (not defined $mod_passwd) { next; }
474         if (not exists $passwd_hash->{$mod_passwd}) {
475             $passwd_hash->{$mod_passwd} = $mod_name;
477         # escalates critical error
478         } else {
479             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
480             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
481             exit( -1 );
482         }
483     }
488 #===  FUNCTION  ================================================================
489 #         NAME:  sig_int_handler
490 #   PARAMETERS:  signal - string - signal arose from system
491 #      RETURNS:  nothing
492 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
493 #===============================================================================
494 sub sig_int_handler {
495     my ($signal) = @_;
497 #       if (defined($ldap_handle)) {
498 #               $ldap_handle->disconnect;
499 #       }
500     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
501     
503     daemon_log("shutting down gosa-si-server", 1);
504     system("kill `ps -C gosa-si-server -o pid=`");
506 $SIG{INT} = \&sig_int_handler;
509 sub check_key_and_xml_validity {
510     my ($crypted_msg, $module_key, $session_id) = @_;
511     my $msg;
512     my $msg_hash;
513     my $error_string;
514     eval{
515         $msg = &decrypt_msg($crypted_msg, $module_key);
517         if ($msg =~ /<xml>/i){
518             $msg =~ s/\s+/ /g;  # just for better daemon_log
519             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
520             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
522             ##############
523             # check header
524             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
525             my $header_l = $msg_hash->{'header'};
526             if( 1 > @{$header_l} ) { die 'empty header tag'; }
527             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
528             my $header = @{$header_l}[0];
529             if( 0 == length $header) { die 'empty string in header tag'; }
531             ##############
532             # check source
533             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
534             my $source_l = $msg_hash->{'source'};
535             if( 1 > @{$source_l} ) { die 'empty source tag'; }
536             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
537             my $source = @{$source_l}[0];
538             if( 0 == length $source) { die 'source error'; }
540             ##############
541             # check target
542             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
543             my $target_l = $msg_hash->{'target'};
544             if( 1 > @{$target_l} ) { die 'empty target tag'; }
545         }
546     };
547     if($@) {
548         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
549         $msg = undef;
550         $msg_hash = undef;
551     }
553     return ($msg, $msg_hash);
557 sub check_outgoing_xml_validity {
558     my ($msg, $session_id) = @_;
560     my $msg_hash;
561     eval{
562         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
564         ##############
565         # check header
566         my $header_l = $msg_hash->{'header'};
567         if( 1 != @{$header_l} ) {
568             die 'no or more than one headers specified';
569         }
570         my $header = @{$header_l}[0];
571         if( 0 == length $header) {
572             die 'header has length 0';
573         }
575         ##############
576         # check source
577         my $source_l = $msg_hash->{'source'};
578         if( 1 != @{$source_l} ) {
579             die 'no or more than 1 sources specified';
580         }
581         my $source = @{$source_l}[0];
582         if( 0 == length $source) {
583             die 'source has length 0';
584         }
585         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
586                 $source =~ /^GOSA$/i ) {
587             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
588         }
589         
590         ##############
591         # check target  
592         my $target_l = $msg_hash->{'target'};
593         if( 0 == @{$target_l} ) {
594             die "no targets specified";
595         }
596         foreach my $target (@$target_l) {
597             if( 0 == length $target) {
598                 die "target has length 0";
599             }
600             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
601                     $target =~ /^GOSA$/i ||
602                     $target =~ /^\*$/ ||
603                     $target =~ /KNOWN_SERVER/i ||
604                     $target =~ /JOBDB/i ||
605                     $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 ){
606                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
607             }
608         }
609     };
610     if($@) {
611         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
612         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
613         $msg_hash = undef;
614     }
616     return ($msg_hash);
620 sub input_from_known_server {
621     my ($input, $remote_ip, $session_id) = @_ ;  
622     my ($msg, $msg_hash, $module);
624     my $sql_statement= "SELECT * FROM known_server";
625     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
627     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
628         my $host_name = $hit->{hostname};
629         if( not $host_name =~ "^$remote_ip") {
630             next;
631         }
632         my $host_key = $hit->{hostkey};
633         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
634         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
636         # check if module can open msg envelope with module key
637         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
638         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
639             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
640             daemon_log("$@", 8);
641             next;
642         }
643         else {
644             $msg = $tmp_msg;
645             $msg_hash = $tmp_msg_hash;
646             $module = "ServerPackages";
647             last;
648         }
649     }
651     if( (!$msg) || (!$msg_hash) || (!$module) ) {
652         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
653     }
654   
655     return ($msg, $msg_hash, $module);
659 sub input_from_known_client {
660     my ($input, $remote_ip, $session_id) = @_ ;  
661     my ($msg, $msg_hash, $module);
663     my $sql_statement= "SELECT * FROM known_clients";
664     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
665     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
666         my $host_name = $hit->{hostname};
667         if( not $host_name =~ /^$remote_ip:\d*$/) {
668                 next;
669                 }
670         my $host_key = $hit->{hostkey};
671         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
672         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
674         # check if module can open msg envelope with module key
675         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
677         if( (!$msg) || (!$msg_hash) ) {
678             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
679             &daemon_log("$@", 8);
680             next;
681         }
682         else {
683             $module = "ClientPackages";
684             last;
685         }
686     }
688     if( (!$msg) || (!$msg_hash) || (!$module) ) {
689         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
690     }
692     return ($msg, $msg_hash, $module);
696 sub input_from_unknown_host {
697     no strict "refs";
698     my ($input, $session_id) = @_ ;
699     my ($msg, $msg_hash, $module);
700     my $error_string;
701     
702         my %act_modules = %$known_modules;
703         
704     while( my ($mod, $info) = each(%act_modules)) {
706         # check a key exists for this module
707         my $module_key = ${$mod."_key"};
708         if( not defined $module_key ) {
709             if( $mod eq 'ArpHandler' ) {
710                 next;
711             }
712             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
713             next;
714         }
715         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
717         # check if module can open msg envelope with module key
718         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
719         if( (not defined $msg) || (not defined $msg_hash) ) {
720             next;
721         }
722         else {
723             $module = $mod;
724             last;
725         }
726     }
728     if( (!$msg) || (!$msg_hash) || (!$module)) {
729         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
730     }
732     return ($msg, $msg_hash, $module);
736 sub create_ciphering {
737     my ($passwd) = @_;
738         if((!defined($passwd)) || length($passwd)==0) {
739                 $passwd = "";
740         }
741     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
742     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
743     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
744     $my_cipher->set_iv($iv);
745     return $my_cipher;
749 sub encrypt_msg {
750     my ($msg, $key) = @_;
751     my $my_cipher = &create_ciphering($key);
752     my $len;
753     {
754             use bytes;
755             $len= 16-length($msg)%16;
756     }
757     $msg = "\0"x($len).$msg;
758     $msg = $my_cipher->encrypt($msg);
759     chomp($msg = &encode_base64($msg));
760     # there are no newlines allowed inside msg
761     $msg=~ s/\n//g;
762     return $msg;
766 sub decrypt_msg {
768     my ($msg, $key) = @_ ;
769     $msg = &decode_base64($msg);
770     my $my_cipher = &create_ciphering($key);
771     $msg = $my_cipher->decrypt($msg); 
772     $msg =~ s/\0*//g;
773     return $msg;
777 sub get_encrypt_key {
778     my ($target) = @_ ;
779     my $encrypt_key;
780     my $error = 0;
782     # target can be in known_server
783     if( not defined $encrypt_key ) {
784         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
785         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
786         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
787             my $host_name = $hit->{hostname};
788             if( $host_name ne $target ) {
789                 next;
790             }
791             $encrypt_key = $hit->{hostkey};
792             last;
793         }
794     }
796     # target can be in known_client
797     if( not defined $encrypt_key ) {
798         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
799         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
800         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
801             my $host_name = $hit->{hostname};
802             if( $host_name ne $target ) {
803                 next;
804             }
805             $encrypt_key = $hit->{hostkey};
806             last;
807         }
808     }
810     return $encrypt_key;
814 #===  FUNCTION  ================================================================
815 #         NAME:  open_socket
816 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
817 #                [PeerPort] string necessary if port not appended by PeerAddr
818 #      RETURNS:  socket IO::Socket::INET
819 #  DESCRIPTION:  open a socket to PeerAddr
820 #===============================================================================
821 sub open_socket {
822     my ($PeerAddr, $PeerPort) = @_ ;
823     if(defined($PeerPort)){
824         $PeerAddr = $PeerAddr.":".$PeerPort;
825     }
826     my $socket;
827     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
828             Porto => "tcp",
829             Type => SOCK_STREAM,
830             Timeout => 5,
831             );
832     if(not defined $socket) {
833         return;
834     }
835 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
836     return $socket;
840 #sub get_local_ip_for_remote_ip {
841 #       my $remote_ip= shift;
842 #       my $result="0.0.0.0";
844 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
845 #               if($remote_ip eq "127.0.0.1") {
846 #                       $result = "127.0.0.1";
847 #               } else {
848 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
850 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
851 #                               or die "Could not open $PROC_NET_ROUTE";
853 #                       my @ifs = <PROC_NET_ROUTE>;
855 #                       close(PROC_NET_ROUTE);
857 #                       # Eat header line
858 #                       shift @ifs;
859 #                       chomp @ifs;
860 #                       foreach my $line(@ifs) {
861 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
862 #                               my $destination;
863 #                               my $mask;
864 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
865 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
866 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
867 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
868 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
869 #                                       # destination matches route, save mac and exit
870 #                                       $result= &get_ip($Iface);
871 #                                       last;
872 #                               }
873 #                       }
874 #               }
875 #       } else {
876 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
877 #       }
878 #       return $result;
879 #}
882 sub send_msg_to_target {
883     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
884     my $error = 0;
885     my $header;
886     my $timestamp = &get_time();
887     my $new_status;
888     my $act_status;
889     my ($sql_statement, $res);
890   
891     if( $msg_header ) {
892         $header = "'$msg_header'-";
893     } else {
894         $header = "";
895     }
897         # Patch the source ip
898         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
899                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
900                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
901         }
903     # encrypt xml msg
904     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
906     # opensocket
907     my $socket = &open_socket($address);
908     if( !$socket ) {
909         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
910         $error++;
911     }
912     
913     if( $error == 0 ) {
914         # send xml msg
915         print $socket $crypted_msg."\n";
917         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
918         daemon_log("$session_id DEBUG: message:\n$msg", 9);
919         
920     }
922     # close socket in any case
923     if( $socket ) {
924         close $socket;
925     }
927     if( $error > 0 ) { $new_status = "down"; }
928     else { $new_status = $msg_header; }
931     # known_clients
932     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
933     $res = $known_clients_db->select_dbentry($sql_statement);
934     if( keys(%$res) == 1) {
935         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
936         if ($act_status eq "down" && $new_status eq "down") {
937             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
938             $res = $known_clients_db->del_dbentry($sql_statement);
939             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
940         } else { 
941             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
942             $res = $known_clients_db->update_dbentry($sql_statement);
943             if($new_status eq "down"){
944                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
945             } else {
946                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
947             }
948         }
949     }
951     # known_server
952     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
953     $res = $known_server_db->select_dbentry($sql_statement);
954     if( keys(%$res) == 1) {
955         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
956         if ($act_status eq "down" && $new_status eq "down") {
957             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
958             $res = $known_server_db->del_dbentry($sql_statement);
959             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
960         } 
961         else { 
962             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
963             $res = $known_server_db->update_dbentry($sql_statement);
964             if($new_status eq "down"){
965                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
966             } else {
967                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
968             }
969         }
970     }
971     return $error; 
975 sub update_jobdb_status_for_send_msgs {
976     my ($answer, $error) = @_;
977     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
978         my $jobdb_id = $1;
979             
980         # sending msg faild
981         if( $error ) {
982             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
983                 my $sql_statement = "UPDATE $job_queue_tn ".
984                     "SET status='error', result='can not deliver msg, please consult log file' ".
985                     "WHERE id=$jobdb_id";
986                 my $res = $job_db->update_dbentry($sql_statement);
987             }
989         # sending msg was successful
990         } else {
991             my $sql_statement = "UPDATE $job_queue_tn ".
992                 "SET status='done' ".
993                 "WHERE id=$jobdb_id AND status='processed'";
994             my $res = $job_db->update_dbentry($sql_statement);
995         }
996     }
1000 sub sig_handler {
1001         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1002         daemon_log("0 INFO got signal '$signal'", 1); 
1003         $kernel->sig_handled();
1004         return;
1008 sub msg_to_decrypt {
1009     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1010     my $session_id = $session->ID;
1011     my ($msg, $msg_hash, $module);
1012     my $error = 0;
1014     # hole neue msg aus @msgs_to_decrypt
1015     my $next_msg = shift @msgs_to_decrypt;
1016     
1017     # entschlüssle sie
1019     # msg is from a new client or gosa
1020     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1021     # msg is from a gosa-si-server
1022     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1023         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1024     }
1025     # msg is from a gosa-si-client
1026     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1027         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1028     }
1029     # an error occurred
1030     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1031         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1032         # could not understand a msg from its server the client cause a re-registering process
1033         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1034                 "' to cause a re-registering of the client if necessary", 3);
1035         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1036         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1037         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1038             my $host_name = $hit->{'hostname'};
1039             my $host_key = $hit->{'hostkey'};
1040             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1041             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1042             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1043         }
1044         $error++;
1045     }
1048     my $header;
1049     my $target;
1050     my $source;
1051     my $done = 0;
1052     my $sql;
1053     my $res;
1055     # check whether this message should be processed here
1056     if ($error == 0) {
1057         $header = @{$msg_hash->{'header'}}[0];
1058         $target = @{$msg_hash->{'target'}}[0];
1059         $source = @{$msg_hash->{'source'}}[0];
1060                 my $not_found_in_known_clients_db = 0;
1061                 my $not_found_in_known_server_db = 0;
1062                 my $not_found_in_foreign_clients_db = 0;
1063         my $local_address;
1064         my $local_mac;
1065         my ($target_ip, $target_port) = split(':', $target);
1066         
1067         # Determine the local ip address if target is an ip address
1068                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1069                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1070                 } else {
1071             $local_address = $server_address;
1072         }
1074         # Determine the local mac address if target is a mac address
1075         if ($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) {
1076             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1077             my $network_interface= &get_interface_for_ip($loc_ip);
1078             $local_mac = &get_mac_for_interface($network_interface);
1079         } else {
1080             $local_mac = $server_mac_address;
1081         }
1083         # target and source is equal to GOSA -> process here
1084         if (not $done) {
1085             if ($target eq "GOSA" && $source eq "GOSA") {
1086                 $done = 1;                    
1087             }
1088         }
1090         # target is own address without forward_to_gosa-tag -> process here
1091         if (not $done) {
1092             #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1093             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1094                 $done = 1;
1095                 if ($source eq "GOSA") {
1096                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1097                 }
1098                 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1099             }
1100         }
1102         # target is a client address in known_clients -> process here
1103                 if (not $done) {
1104                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1105                                 $res = $known_clients_db->select_dbentry($sql);
1106                                 if (keys(%$res) > 0) {
1107                                                 $done = 1; 
1108                                                 my $hostname = $res->{1}->{'hostname'};
1109                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1110                         #print STDERR "target is a client address in known_clients -> process here\n";
1111                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1112                         if ($source eq "GOSA") {
1113                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1114                         }
1116                                 } else {
1117                                                 $not_found_in_known_clients_db = 1;
1118                                 }
1119                 }
1120         
1121         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1122         if (not $done) {
1123             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1124             my $gosa_at;
1125             my $gosa_session_id;
1126             if (($target eq $local_address) && (defined $forward_to_gosa)){
1127                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1128                 if ($gosa_at ne $local_address) {
1129                     $done = 1;
1130                     #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1131                 }
1132             }
1133         }
1135         # if message should be processed here -> add message to incoming_db
1136                 if ($done) {
1137                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1138                                 # so gosa-si-server knows how to process this kind of messages
1139                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1140                                                 $module = "GosaPackages";
1141                                 }
1143                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1144                                                                 primkey=>[],
1145                                                                 headertag=>$header,
1146                                                                 targettag=>$target,
1147                                                                 xmlmessage=>&encode_base64($msg),
1148                                                                 timestamp=>&get_time,
1149                                                                 module=>$module,
1150                                                                 sessionid=>$session_id,
1151                                                                 } );
1152                 }
1154         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1155         if (not $done) {
1156             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1157             my $gosa_at;
1158             my $gosa_session_id;
1159             if (($target eq $local_address) && (defined $forward_to_gosa)){
1160                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1161                 if ($gosa_at eq $local_address) {
1162                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1163                     if( defined $session_reference ) {
1164                         $heap = $session_reference->get_heap();
1165                     }
1166                     if(exists $heap->{'client'}) {
1167                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1168                         $heap->{'client'}->put($msg);
1169                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1170                     }
1171                     $done = 1;
1172                     #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1173                 }
1174             }
1176         }
1178         # target is a client address in foreign_clients -> forward to registration server
1179         if (not $done) {
1180             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1181             $res = $foreign_clients_db->select_dbentry($sql);
1182             if (keys(%$res) > 0) {
1183                     my $hostname = $res->{1}->{'hostname'};
1184                     my ($host_ip, $host_port) = split(/:/, $hostname);
1185                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1186                 my $regserver = $res->{1}->{'regserver'};
1187                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1188                 my $res = $known_server_db->select_dbentry($sql);
1189                 if (keys(%$res) > 0) {
1190                     my $regserver_key = $res->{1}->{'hostkey'};
1191                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1192                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1193                     if ($source eq "GOSA") {
1194                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1195                     }
1196                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1197                 }
1198                 $done = 1;
1199                 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1200             } else {
1201                                 $not_found_in_foreign_clients_db = 1;
1202                         }
1203         }
1205         # target is a server address -> forward to server
1206         if (not $done) {
1207             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1208             $res = $known_server_db->select_dbentry($sql);
1209             if (keys(%$res) > 0) {
1210                 my $hostkey = $res->{1}->{'hostkey'};
1212                 if ($source eq "GOSA") {
1213                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1214                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1216                 }
1218                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1219                 $done = 1;
1220             } else {
1221                                 $not_found_in_known_server_db = 1;
1222                         }
1223         }
1225                 
1226                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1227                 if ( $not_found_in_foreign_clients_db 
1228                                                 && $not_found_in_known_server_db
1229                                                 && $not_found_in_known_clients_db) {
1230                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1231                                                                 primkey=>[],
1232                                                                 headertag=>$header,
1233                                                                 targettag=>$target,
1234                                                                 xmlmessage=>&encode_base64($msg),
1235                                                                 timestamp=>&get_time,
1236                                                                 module=>$module,
1237                                                                 sessionid=>$session_id,
1238                                                                 } );
1239                                 $done = 1;
1240                 }
1243         if (not $done) {
1244             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1245             if ($source eq "GOSA") {
1246                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1247                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1249                 my $session_reference = $kernel->ID_id_to_session($session_id);
1250                 if( defined $session_reference ) {
1251                     $heap = $session_reference->get_heap();
1252                 }
1253                 if(exists $heap->{'client'}) {
1254                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1255                     $heap->{'client'}->put($error_msg);
1256                 }
1257             }
1258         }
1260     }
1262     return;
1266 sub next_task {
1267     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1268     my $running_task = POE::Wheel::Run->new(
1269             Program => sub { process_task($session, $heap, $task) },
1270             StdioFilter => POE::Filter::Reference->new(),
1271             StdoutEvent  => "task_result",
1272             StderrEvent  => "task_debug",
1273             CloseEvent   => "task_done",
1274             );
1275     $heap->{task}->{ $running_task->ID } = $running_task;
1278 sub handle_task_result {
1279     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1280     my $client_answer = $result->{'answer'};
1281     if( $client_answer =~ s/session_id=(\d+)$// ) {
1282         my $session_id = $1;
1283         if( defined $session_id ) {
1284             my $session_reference = $kernel->ID_id_to_session($session_id);
1285             if( defined $session_reference ) {
1286                 $heap = $session_reference->get_heap();
1287             }
1288         }
1290         if(exists $heap->{'client'}) {
1291             $heap->{'client'}->put($client_answer);
1292         }
1293     }
1294     $kernel->sig(CHLD => "child_reap");
1297 sub handle_task_debug {
1298     my $result = $_[ARG0];
1299     print STDERR "$result\n";
1302 sub handle_task_done {
1303     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1304     delete $heap->{task}->{$task_id};
1307 sub process_task {
1308     no strict "refs";
1309     #CHECK: Not @_[...]?
1310     my ($session, $heap, $task) = @_;
1311     my $error = 0;
1312     my $answer_l;
1313     my ($answer_header, @answer_target_l, $answer_source);
1314     my $client_answer = "";
1316     # prepare all variables needed to process message
1317     #my $msg = $task->{'xmlmessage'};
1318     my $msg = &decode_base64($task->{'xmlmessage'});
1319     my $incoming_id = $task->{'id'};
1320     my $module = $task->{'module'};
1321     my $header =  $task->{'headertag'};
1322     my $session_id = $task->{'sessionid'};
1323     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1324     my $source = @{$msg_hash->{'source'}}[0];
1325     
1326     # set timestamp of incoming client uptodate, so client will not 
1327     # be deleted from known_clients because of expiration
1328     my $act_time = &get_time();
1329     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1330     my $res = $known_clients_db->exec_statement($sql);
1332     ######################
1333     # process incoming msg
1334     if( $error == 0) {
1335         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1336         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1337         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1339         if ( 0 < @{$answer_l} ) {
1340             my $answer_str = join("\n", @{$answer_l});
1341             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1342                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1343             }
1344             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1345         } else {
1346             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1347         }
1349     }
1350     if( !$answer_l ) { $error++ };
1352     ########
1353     # answer
1354     if( $error == 0 ) {
1356         foreach my $answer ( @{$answer_l} ) {
1357             # check outgoing msg to xml validity
1358             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1359             if( not defined $answer_hash ) { next; }
1360             
1361             $answer_header = @{$answer_hash->{'header'}}[0];
1362             @answer_target_l = @{$answer_hash->{'target'}};
1363             $answer_source = @{$answer_hash->{'source'}}[0];
1365             # deliver msg to all targets 
1366             foreach my $answer_target ( @answer_target_l ) {
1368                 # targets of msg are all gosa-si-clients in known_clients_db
1369                 if( $answer_target eq "*" ) {
1370                     # answer is for all clients
1371                     my $sql_statement= "SELECT * FROM known_clients";
1372                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1373                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1374                         my $host_name = $hit->{hostname};
1375                         my $host_key = $hit->{hostkey};
1376                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1377                         &update_jobdb_status_for_send_msgs($answer, $error);
1378                     }
1379                 }
1381                 # targets of msg are all gosa-si-server in known_server_db
1382                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1383                     # answer is for all server in known_server
1384                     my $sql_statement= "SELECT * FROM $known_server_tn";
1385                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1386                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1387                         my $host_name = $hit->{hostname};
1388                         my $host_key = $hit->{hostkey};
1389                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1390                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1391                         &update_jobdb_status_for_send_msgs($answer, $error);
1392                     }
1393                 }
1395                 # target of msg is GOsa
1396                                 elsif( $answer_target eq "GOSA" ) {
1397                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1398                                         my $add_on = "";
1399                     if( defined $session_id ) {
1400                         $add_on = ".session_id=$session_id";
1401                     }
1402                     # answer is for GOSA and has to returned to connected client
1403                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1404                     $client_answer = $gosa_answer.$add_on;
1405                 }
1407                 # target of msg is job queue at this host
1408                 elsif( $answer_target eq "JOBDB") {
1409                     $answer =~ /<header>(\S+)<\/header>/;   
1410                     my $header;
1411                     if( defined $1 ) { $header = $1; }
1412                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1413                     &update_jobdb_status_for_send_msgs($answer, $error);
1414                 }
1416                 # Target of msg is a mac address
1417                 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 ) {
1418                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1419                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1420                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1421                     my $found_ip_flag = 0;
1422                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1423                         my $host_name = $hit->{hostname};
1424                         my $host_key = $hit->{hostkey};
1425                         $answer =~ s/$answer_target/$host_name/g;
1426                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1427                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1428                         &update_jobdb_status_for_send_msgs($answer, $error);
1429                         $found_ip_flag++ ;
1430                     }   
1431                     if ($found_ip_flag == 0) {
1432                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1433                         my $res = $foreign_clients_db->select_dbentry($sql);
1434                         while( my ($hit_num, $hit) = each %{ $res } ) {
1435                             my $host_name = $hit->{hostname};
1436                             my $reg_server = $hit->{regserver};
1437                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1438                             
1439                             # Fetch key for reg_server
1440                             my $reg_server_key;
1441                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1442                             my $res = $known_server_db->select_dbentry($sql);
1443                             if (exists $res->{1}) {
1444                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1445                             } else {
1446                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1447                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1448                                 $reg_server_key = undef;
1449                             }
1451                             # Send answer to server where client is registered
1452                             if (defined $reg_server_key) {
1453                                 $answer =~ s/$answer_target/$host_name/g;
1454                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1455                                 &update_jobdb_status_for_send_msgs($answer, $error);
1456                                 $found_ip_flag++ ;
1457                             }
1458                         }
1459                     }
1460                     if( $found_ip_flag == 0) {
1461                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1462                     }
1464                 # Answer is for one specific host   
1465                 } else {
1466                     # get encrypt_key
1467                     my $encrypt_key = &get_encrypt_key($answer_target);
1468                     if( not defined $encrypt_key ) {
1469                         # unknown target
1470                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1471                         next;
1472                     }
1473                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1474                     &update_jobdb_status_for_send_msgs($answer, $error);
1475                 }
1476             }
1477         }
1478     }
1480     my $filter = POE::Filter::Reference->new();
1481     my %result = ( 
1482             status => "seems ok to me",
1483             answer => $client_answer,
1484             );
1486     my $output = $filter->put( [ \%result ] );
1487     print @$output;
1492 sub session_start {
1493     my ($kernel) = $_[KERNEL];
1494     $global_kernel = $kernel;
1495     $kernel->yield('register_at_foreign_servers');
1496         $kernel->yield('create_fai_server_db', $fai_server_tn );
1497         $kernel->yield('create_fai_release_db', $fai_release_tn );
1498     $kernel->yield('watch_for_next_tasks');
1499         $kernel->sig(USR1 => "sig_handler");
1500         $kernel->sig(USR2 => "recreate_packages_db");
1501         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1502         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1503     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1504         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1505     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1506         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1507     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1509     # Start opsi check
1510     if ($opsi_enabled eq "true") {
1511         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1512     }
1517 sub watch_for_done_jobs {
1518     #CHECK: $heap for what?
1519     my ($kernel,$heap) = @_[KERNEL, HEAP];
1521     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1522         my $res = $job_db->select_dbentry( $sql_statement );
1524     while( my ($id, $hit) = each %{$res} ) {
1525         my $jobdb_id = $hit->{id};
1526         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1527         my $res = $job_db->del_dbentry($sql_statement); 
1528     }
1530     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1534 sub watch_for_opsi_jobs {
1535     my ($kernel) = $_[KERNEL];
1537     # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a 
1538     # opsi install job is to parse the xml message. There is still the correct header.
1539     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1540         my $res = $job_db->select_dbentry( $sql_statement );
1542     # Ask OPSI for an update of the running jobs
1543     while (my ($id, $hit) = each %$res ) {
1544         # Determine current parameters of the job
1545         my $hostId = $hit->{'plainname'};
1546         my $macaddress = $hit->{'macaddress'};
1547         my $progress = $hit->{'progress'};
1549         my $result= {};
1550         
1551         # For hosts, only return the products that are or get installed
1552         my $callobj;
1553         $callobj = {
1554             method  => 'getProductStates_hash',
1555             params  => [ $hostId ],
1556             id  => 1,
1557         };
1558         
1559         my $hres = $opsi_client->call($opsi_url, $callobj);
1560         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1561         if (not &check_opsi_res($hres)) {
1562             my $htmp= $hres->result->{$hostId};
1563         
1564             # Check state != not_installed or action == setup -> load and add
1565             my $products= 0;
1566             my $installed= 0;
1567             my $installing = 0;
1568             my $error= 0;  
1569             my @installed_list;
1570             my @error_list;
1571             my $act_status = "none";
1572             foreach my $product (@{$htmp}){
1574                 if ($product->{'installationStatus'} ne "not_installed" or
1575                         $product->{'actionRequest'} eq "setup"){
1577                     # Increase number of products for this host
1578                     $products++;
1579         
1580                     if ($product->{'installationStatus'} eq "failed"){
1581                         $result->{$product->{'productId'}}= "error";
1582                         unshift(@error_list, $product->{'productId'});
1583                         $error++;
1584                     }
1585                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1586                         $result->{$product->{'productId'}}= "installed";
1587                         unshift(@installed_list, $product->{'productId'});
1588                         $installed++;
1589                     }
1590                     if ($product->{'installationStatus'} eq "installing"){
1591                         $result->{$product->{'productId'}}= "installing";
1592                         $installing++;
1593                         $act_status = "installing - ".$product->{'productId'};
1594                     }
1595                 }
1596             }
1597         
1598             # Estimate "rough" progress, avoid division by zero
1599             if ($products == 0) {
1600                 $result->{'progress'}= 0;
1601             } else {
1602                 $result->{'progress'}= int($installed * 100 / $products);
1603             }
1605             # Set updates in job queue
1606             if ((not $error) && (not $installing) && ($installed)) {
1607                 $act_status = "installed - ".join(", ", @installed_list);
1608             }
1609             if ($error) {
1610                 $act_status = "error - ".join(", ", @error_list);
1611             }
1612             if ($progress ne $result->{'progress'} ) {
1613                 # Updating progress and result 
1614                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1615                 my $update_res = $job_db->update_dbentry($update_statement);
1616             }
1617             if ($progress eq 100) { 
1618                 # Updateing status
1619                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1620                 if ($error) {
1621                     $done_statement .= "status='error'";
1622                 } else {
1623                     $done_statement .= "status='done'";
1624                 }
1625                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1626                 my $done_res = $job_db->update_dbentry($done_statement);
1627             }
1630         }
1631     }
1633     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1637 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1638 sub watch_for_modified_jobs {
1639     my ($kernel,$heap) = @_[KERNEL, HEAP];
1641     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1642     my $res = $job_db->select_dbentry( $sql_statement );
1643     
1644     # if db contains no jobs which should be update, do nothing
1645     if (keys %$res != 0) {
1647         if ($job_synchronization  eq "true") {
1648             # make out of the db result a gosa-si message   
1649             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1650  
1651             # update all other SI-server
1652             &inform_all_other_si_server($update_msg);
1653         }
1655         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1656         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1657         $res = $job_db->update_dbentry($sql_statement);
1658     }
1660     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1664 sub watch_for_new_jobs {
1665         if($watch_for_new_jobs_in_progress == 0) {
1666                 $watch_for_new_jobs_in_progress = 1;
1667                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1669                 # check gosa job quaeue for jobs with executable timestamp
1670                 my $timestamp = &get_time();
1671                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1672                 my $res = $job_db->exec_statement( $sql_statement );
1674                 # Merge all new jobs that would do the same actions
1675                 my @drops;
1676                 my $hits;
1677                 foreach my $hit (reverse @{$res} ) {
1678                         my $macaddress= lc @{$hit}[8];
1679                         my $headertag= @{$hit}[5];
1680                         if(
1681                                 defined($hits->{$macaddress}) &&
1682                                 defined($hits->{$macaddress}->{$headertag}) &&
1683                                 defined($hits->{$macaddress}->{$headertag}[0])
1684                         ) {
1685                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1686                         }
1687                         $hits->{$macaddress}->{$headertag}= $hit;
1688                 }
1690                 # Delete new jobs with a matching job in state 'processing'
1691                 foreach my $macaddress (keys %{$hits}) {
1692                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1693                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1694                                 if(defined($jobdb_id)) {
1695                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1696                                         my $res = $job_db->exec_statement( $sql_statement );
1697                                         foreach my $hit (@{$res}) {
1698                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1699                                         }
1700                                 } else {
1701                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1702                                 }
1703                         }
1704                 }
1706                 # Commit deletion
1707                 $job_db->exec_statementlist(\@drops);
1709                 # Look for new jobs that could be executed
1710                 foreach my $macaddress (keys %{$hits}) {
1712                         # Look if there is an executing job
1713                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1714                         my $res = $job_db->exec_statement( $sql_statement );
1716                         # Skip new jobs for host if there is a processing job
1717                         if(defined($res) and defined @{$res}[0]) {
1718                                 next;
1719                         }
1721                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1722                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1723                                 if(defined($jobdb_id)) {
1724                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1726                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1727                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1728                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1730                                         # expect macaddress is unique!!!!!!
1731                                         my $target = $res_hash->{1}->{hostname};
1733                                         # change header
1734                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1736                                         # add sqlite_id
1737                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1739                                         $job_msg =~ /<header>(\S+)<\/header>/;
1740                                         my $header = $1 ;
1741                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1743                                         # update status in job queue to 'processing'
1744                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1745                                         my $res = $job_db->update_dbentry($sql_statement);
1746 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1748                                         # We don't want parallel processing
1749                                         last;
1750                                 }
1751                         }
1752                 }
1754                 $watch_for_new_jobs_in_progress = 0;
1755                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1756         }
1760 sub watch_for_new_messages {
1761     my ($kernel,$heap) = @_[KERNEL, HEAP];
1762     my @coll_user_msg;   # collection list of outgoing messages
1763     
1764     # check messaging_db for new incoming messages with executable timestamp
1765     my $timestamp = &get_time();
1766     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1767     my $res = $messaging_db->exec_statement( $sql_statement );
1768         foreach my $hit (@{$res}) {
1770         # create outgoing messages
1771         my $message_to = @{$hit}[3];
1772         # translate message_to to plain login name
1773         my @message_to_l = split(/,/, $message_to);  
1774                 my %receiver_h; 
1775                 foreach my $receiver (@message_to_l) {
1776                         if ($receiver =~ /^u_([\s\S]*)$/) {
1777                                 $receiver_h{$1} = 0;
1778                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1779                                 my $group_name = $1;
1780                                 # fetch all group members from ldap and add them to receiver hash
1781                                 my $ldap_handle = &get_ldap_handle();
1782                                 if (defined $ldap_handle) {
1783                                                 my $mesg = $ldap_handle->search(
1784                                                                                 base => $ldap_base,
1785                                                                                 scope => 'sub',
1786                                                                                 attrs => ['memberUid'],
1787                                                                                 filter => "cn=$group_name",
1788                                                                                 );
1789                                                 if ($mesg->count) {
1790                                                                 my @entries = $mesg->entries;
1791                                                                 foreach my $entry (@entries) {
1792                                                                                 my @receivers= $entry->get_value("memberUid");
1793                                                                                 foreach my $receiver (@receivers) { 
1794                                                                                                 $receiver_h{$1} = 0;
1795                                                                                 }
1796                                                                 }
1797                                                 } 
1798                                                 # translating errors ?
1799                                                 if ($mesg->code) {
1800                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1801                                                 }
1802                                 # ldap handle error ?           
1803                                 } else {
1804                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1805                                 }
1806                         } else {
1807                                 my $sbjct = &encode_base64(@{$hit}[1]);
1808                                 my $msg = &encode_base64(@{$hit}[7]);
1809                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1810                         }
1811                 }
1812                 my @receiver_l = keys(%receiver_h);
1814         my $message_id = @{$hit}[0];
1816         #add each outgoing msg to messaging_db
1817         my $receiver;
1818         foreach $receiver (@receiver_l) {
1819             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1820                 "VALUES ('".
1821                 $message_id."', '".    # id
1822                 @{$hit}[1]."', '".     # subject
1823                 @{$hit}[2]."', '".     # message_from
1824                 $receiver."', '".      # message_to
1825                 "none"."', '".         # flag
1826                 "out"."', '".          # direction
1827                 @{$hit}[6]."', '".     # delivery_time
1828                 @{$hit}[7]."', '".     # message
1829                 $timestamp."'".     # timestamp
1830                 ")";
1831             &daemon_log("M DEBUG: $sql_statement", 1);
1832             my $res = $messaging_db->exec_statement($sql_statement);
1833             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1834         }
1836         # set incoming message to flag d=deliverd
1837         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1838         &daemon_log("M DEBUG: $sql_statement", 7);
1839         $res = $messaging_db->update_dbentry($sql_statement);
1840         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1841     }
1843     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1844     return;
1847 sub watch_for_delivery_messages {
1848     my ($kernel, $heap) = @_[KERNEL, HEAP];
1850     # select outgoing messages
1851     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1852     #&daemon_log("0 DEBUG: $sql", 7);
1853     my $res = $messaging_db->exec_statement( $sql_statement );
1854     
1855     # build out msg for each    usr
1856     foreach my $hit (@{$res}) {
1857         my $receiver = @{$hit}[3];
1858         my $msg_id = @{$hit}[0];
1859         my $subject = @{$hit}[1];
1860         my $message = @{$hit}[7];
1862         # resolve usr -> host where usr is logged in
1863         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1864         #&daemon_log("0 DEBUG: $sql", 7);
1865         my $res = $login_users_db->exec_statement($sql);
1867         # reciver is logged in nowhere
1868         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1870                 my $send_succeed = 0;
1871                 foreach my $hit (@$res) {
1872                                 my $receiver_host = @$hit[0];
1873                 my $delivered2host = 0;
1874                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1876                                 # Looking for host in know_clients_db 
1877                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1878                                 my $res = $known_clients_db->exec_statement($sql);
1880                 # Host is known in known_clients_db
1881                 if (ref(@$res[0]) eq "ARRAY") {
1882                     my $receiver_key = @{@{$res}[0]}[2];
1883                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1884                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1885                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1886                     if ($error == 0 ) {
1887                         $send_succeed++ ;
1888                         $delivered2host++ ;
1889                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1890                     } else {
1891                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1892                     }
1893                 }
1894                 
1895                 # Message already send, do not need to do anything more, otherwise ...
1896                 if ($delivered2host) { next;}
1897     
1898                 # ...looking for host in foreign_clients_db
1899                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1900                 $res = $foreign_clients_db->exec_statement($sql);
1901   
1902                                 # Host is known in foreign_clients_db 
1903                                 if (ref(@$res[0]) eq "ARRAY") { 
1904                     my $registration_server = @{@{$res}[0]}[2];
1905                     
1906                     # Fetch encryption key for registration server
1907                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1908                     my $res = $known_server_db->exec_statement($sql);
1909                     if (ref(@$res[0]) eq "ARRAY") { 
1910                         my $registration_server_key = @{@{$res}[0]}[3];
1911                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1912                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1913                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1914                         if ($error == 0 ) {
1915                             $send_succeed++ ;
1916                             $delivered2host++ ;
1917                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1918                         } else {
1919                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1920                         }
1922                     } else {
1923                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1924                                 "registrated at server '$registration_server', ".
1925                                 "but no data available in known_server_db ", 1); 
1926                     }
1927                 }
1928                 
1929                 if (not $delivered2host) {
1930                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1931                 }
1932                 }
1934                 if ($send_succeed) {
1935                                 # set outgoing msg at db to deliverd
1936                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1937                                 my $res = $messaging_db->exec_statement($sql); 
1938                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1939                 } else {
1940             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1941         }
1942         }
1944     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1945     return;
1949 sub watch_for_done_messages {
1950     my ($kernel,$heap) = @_[KERNEL, HEAP];
1952     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1953     #&daemon_log("0 DEBUG: $sql", 7);
1954     my $res = $messaging_db->exec_statement($sql); 
1956     foreach my $hit (@{$res}) {
1957         my $msg_id = @{$hit}[0];
1959         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1960         #&daemon_log("0 DEBUG: $sql", 7); 
1961         my $res = $messaging_db->exec_statement($sql);
1963         # not all usr msgs have been seen till now
1964         if ( ref(@$res[0]) eq "ARRAY") { next; }
1965         
1966         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1967         #&daemon_log("0 DEBUG: $sql", 7);
1968         $res = $messaging_db->exec_statement($sql);
1969     
1970     }
1972     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1973     return;
1977 sub watch_for_old_known_clients {
1978     my ($kernel,$heap) = @_[KERNEL, HEAP];
1980     my $sql_statement = "SELECT * FROM $known_clients_tn";
1981     my $res = $known_clients_db->select_dbentry( $sql_statement );
1983     my $act_time = int(&get_time());
1985     while ( my ($hit_num, $hit) = each %$res) {
1986         my $expired_timestamp = int($hit->{'timestamp'});
1987         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1988         my $dt = DateTime->new( year   => $1,
1989                 month  => $2,
1990                 day    => $3,
1991                 hour   => $4,
1992                 minute => $5,
1993                 second => $6,
1994                 );
1996         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1997         $expired_timestamp = $dt->ymd('').$dt->hms('');
1998         if ($act_time > $expired_timestamp) {
1999             my $hostname = $hit->{'hostname'};
2000             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2001             my $del_res = $known_clients_db->exec_statement($del_sql);
2003             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2004         }
2006     }
2008     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2012 sub watch_for_next_tasks {
2013     my ($kernel,$heap) = @_[KERNEL, HEAP];
2015     my $sql = "SELECT * FROM $incoming_tn";
2016     my $res = $incoming_db->select_dbentry($sql);
2018     while ( my ($hit_num, $hit) = each %$res) {
2019         my $headertag = $hit->{'headertag'};
2020         if ($headertag =~ /^answer_(\d+)/) {
2021             # do not start processing, this message is for a still running POE::Wheel
2022             next;
2023         }
2024         my $message_id = $hit->{'id'};
2025         $kernel->yield('next_task', $hit);
2027         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2028         my $res = $incoming_db->exec_statement($sql);
2029     }
2031     $kernel->delay_set('watch_for_next_tasks', 0.1); 
2035 sub get_ldap_handle {
2036         my ($session_id) = @_;
2037         my $heap;
2038         my $ldap_handle;
2040         if (not defined $session_id ) { $session_id = 0 };
2041         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2043         if ($session_id == 0) {
2044                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2045                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2046                 $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!"); 
2048         } else {
2049                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2050                 if( defined $session_reference ) {
2051                         $heap = $session_reference->get_heap();
2052                 }
2054                 if (not defined $heap) {
2055                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2056                         return;
2057                 }
2059                 # TODO: This "if" is nonsense, because it doesn't prove that the
2060                 #       used handle is still valid - or if we've to reconnect...
2061                 #if (not exists $heap->{ldap_handle}) {
2062                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2063                         $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!"); 
2064                         $heap->{ldap_handle} = $ldap_handle;
2065                 #}
2066         }
2067         return $ldap_handle;
2071 sub change_fai_state {
2072     my ($st, $targets, $session_id) = @_;
2073     $session_id = 0 if not defined $session_id;
2074     # Set FAI state to localboot
2075     my %mapActions= (
2076         reboot    => '',
2077         update    => 'softupdate',
2078         localboot => 'localboot',
2079         reinstall => 'install',
2080         rescan    => '',
2081         wake      => '',
2082         memcheck  => 'memcheck',
2083         sysinfo   => 'sysinfo',
2084         install   => 'install',
2085     );
2087     # Return if this is unknown
2088     if (!exists $mapActions{ $st }){
2089         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2090       return;
2091     }
2093     my $state= $mapActions{ $st };
2095     my $ldap_handle = &get_ldap_handle($session_id);
2096     if( defined($ldap_handle) ) {
2098       # Build search filter for hosts
2099         my $search= "(&(objectClass=GOhard)";
2100         foreach (@{$targets}){
2101             $search.= "(macAddress=$_)";
2102         }
2103         $search.= ")";
2105       # If there's any host inside of the search string, procress them
2106         if (!($search =~ /macAddress/)){
2107             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2108             return;
2109         }
2111       # Perform search for Unit Tag
2112       my $mesg = $ldap_handle->search(
2113           base   => $ldap_base,
2114           scope  => 'sub',
2115           attrs  => ['dn', 'FAIstate', 'objectClass'],
2116           filter => "$search"
2117           );
2119           if ($mesg->count) {
2120                   my @entries = $mesg->entries;
2121                   if (0 == @entries) {
2122                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2123                   }
2125                   foreach my $entry (@entries) {
2126                           # Only modify entry if it is not set to '$state'
2127                           if ($entry->get_value("FAIstate") ne "$state"){
2128                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2129                                   my $result;
2130                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2131                                   if (exists $tmp{'FAIobject'}){
2132                                           if ($state eq ''){
2133                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2134                                                           delete => [ FAIstate => [] ] ]);
2135                                           } else {
2136                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2137                                                           replace => [ FAIstate => $state ] ]);
2138                                           }
2139                                   } elsif ($state ne ''){
2140                                           $result= $ldap_handle->modify($entry->dn, changes => [
2141                                                   add     => [ objectClass => 'FAIobject' ],
2142                                                   add     => [ FAIstate => $state ] ]);
2143                                   }
2145                                   # Errors?
2146                                   if ($result->code){
2147                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2148                                   }
2149                           } else {
2150                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2151                           }  
2152                   }
2153           } else {
2154                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2155           }
2157     # if no ldap handle defined
2158     } else {
2159         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2160     }
2162         return;
2166 sub change_goto_state {
2167     my ($st, $targets, $session_id) = @_;
2168     $session_id = 0  if not defined $session_id;
2170     # Switch on or off?
2171     my $state= $st eq 'active' ? 'active': 'locked';
2173     my $ldap_handle = &get_ldap_handle($session_id);
2174     if( defined($ldap_handle) ) {
2176       # Build search filter for hosts
2177       my $search= "(&(objectClass=GOhard)";
2178       foreach (@{$targets}){
2179         $search.= "(macAddress=$_)";
2180       }
2181       $search.= ")";
2183       # If there's any host inside of the search string, procress them
2184       if (!($search =~ /macAddress/)){
2185         return;
2186       }
2188       # Perform search for Unit Tag
2189       my $mesg = $ldap_handle->search(
2190           base   => $ldap_base,
2191           scope  => 'sub',
2192           attrs  => ['dn', 'gotoMode'],
2193           filter => "$search"
2194           );
2196       if ($mesg->count) {
2197         my @entries = $mesg->entries;
2198         foreach my $entry (@entries) {
2200           # Only modify entry if it is not set to '$state'
2201           if ($entry->get_value("gotoMode") ne $state){
2203             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2204             my $result;
2205             $result= $ldap_handle->modify($entry->dn, changes => [
2206                                                 replace => [ gotoMode => $state ] ]);
2208             # Errors?
2209             if ($result->code){
2210               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2211             }
2213           }
2214         }
2215       } else {
2216                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2217           }
2219     }
2223 sub run_recreate_packages_db {
2224     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2225     my $session_id = $session->ID;
2226         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2227         $kernel->yield('create_fai_release_db', $fai_release_tn);
2228         $kernel->yield('create_fai_server_db', $fai_server_tn);
2229         return;
2233 sub run_create_fai_server_db {
2234     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2235     my $session_id = $session->ID;
2236     my $task = POE::Wheel::Run->new(
2237             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2238             StdoutEvent  => "session_run_result",
2239             StderrEvent  => "session_run_debug",
2240             CloseEvent   => "session_run_done",
2241             );
2243     $heap->{task}->{ $task->ID } = $task;
2244     return;
2248 sub create_fai_server_db {
2249     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2250         my $result;
2252         if (not defined $session_id) { $session_id = 0; }
2253     my $ldap_handle = &get_ldap_handle();
2254         if(defined($ldap_handle)) {
2255                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2256                 my $mesg= $ldap_handle->search(
2257                         base   => $ldap_base,
2258                         scope  => 'sub',
2259                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2260                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2261                 );
2262                 if($mesg->{'resultCode'} == 0 &&
2263                    $mesg->count != 0) {
2264                    foreach my $entry (@{$mesg->{entries}}) {
2265                            if($entry->exists('FAIrepository')) {
2266                                    # Add an entry for each Repository configured for server
2267                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2268                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2269                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2270                                                    $result= $fai_server_db->add_dbentry( { 
2271                                                                    table => $table_name,
2272                                                                    primkey => ['server', 'release', 'tag'],
2273                                                                    server => $tmp_url,
2274                                                                    release => $tmp_release,
2275                                                                    sections => $tmp_sections,
2276                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2277                                                            } );
2278                                            }
2279                                    }
2280                            }
2281                    }
2282                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2284                 # TODO: Find a way to post the 'create_packages_list_db' event
2285                 if(not defined($dont_create_packages_list)) {
2286                         &create_packages_list_db(undef, undef, $session_id);
2287                 }
2288         }       
2289     
2290     $ldap_handle->disconnect;
2291         return $result;
2295 sub run_create_fai_release_db {
2296     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2297         my $session_id = $session->ID;
2298     my $task = POE::Wheel::Run->new(
2299             Program => sub { &create_fai_release_db($table_name, $session_id) },
2300             StdoutEvent  => "session_run_result",
2301             StderrEvent  => "session_run_debug",
2302             CloseEvent   => "session_run_done",
2303             );
2305     $heap->{task}->{ $task->ID } = $task;
2306     return;
2310 sub create_fai_release_db {
2311         my ($table_name, $session_id) = @_;
2312         my $result;
2314     # used for logging
2315     if (not defined $session_id) { $session_id = 0; }
2317     my $ldap_handle = &get_ldap_handle();
2318         if(defined($ldap_handle)) {
2319                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2320                 my $mesg= $ldap_handle->search(
2321                         base   => $ldap_base,
2322                         scope  => 'sub',
2323                         attrs  => [],
2324                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2325                 );
2326                 if($mesg->{'resultCode'} == 0 &&
2327                         $mesg->count != 0) {
2328                         # Walk through all possible FAI container ou's
2329                         my @sql_list;
2330                         my $timestamp= &get_time();
2331                         foreach my $ou (@{$mesg->{entries}}) {
2332                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2333                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2334                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2335                                         if(@tmp_array) {
2336                                                 foreach my $entry (@tmp_array) {
2337                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2338                                                                 my $sql= 
2339                                                                 "INSERT INTO $table_name "
2340                                                                 ."(timestamp, release, class, type, state) VALUES ("
2341                                                                 .$timestamp.","
2342                                                                 ."'".$entry->{'release'}."',"
2343                                                                 ."'".$entry->{'class'}."',"
2344                                                                 ."'".$entry->{'type'}."',"
2345                                                                 ."'".$entry->{'state'}."')";
2346                                                                 push @sql_list, $sql;
2347                                                         }
2348                                                 }
2349                                         }
2350                                 }
2351                         }
2353                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2354                         if(@sql_list) {
2355                                 unshift @sql_list, "VACUUM";
2356                                 unshift @sql_list, "DELETE FROM $table_name";
2357                                 $fai_release_db->exec_statementlist(\@sql_list);
2358                         }
2359                         daemon_log("$session_id DEBUG: Done with inserting",7);
2360                 }
2361                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2362         }
2363     $ldap_handle->disconnect;
2364         return $result;
2367 sub get_fai_types {
2368         my $tmp_classes = shift || return undef;
2369         my @result;
2371         foreach my $type(keys %{$tmp_classes}) {
2372                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2373                         my $entry = {
2374                                 type => $type,
2375                                 state => $tmp_classes->{$type}[0],
2376                         };
2377                         push @result, $entry;
2378                 }
2379         }
2381         return @result;
2384 sub get_fai_state {
2385         my $result = "";
2386         my $tmp_classes = shift || return $result;
2388         foreach my $type(keys %{$tmp_classes}) {
2389                 if(defined($tmp_classes->{$type}[0])) {
2390                         $result = $tmp_classes->{$type}[0];
2391                         
2392                 # State is equal for all types in class
2393                         last;
2394                 }
2395         }
2397         return $result;
2400 sub resolve_fai_classes {
2401         my ($fai_base, $ldap_handle, $session_id) = @_;
2402         if (not defined $session_id) { $session_id = 0; }
2403         my $result;
2404         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2405         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2406         my $fai_classes;
2408         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2409         my $mesg= $ldap_handle->search(
2410                 base   => $fai_base,
2411                 scope  => 'sub',
2412                 attrs  => ['cn','objectClass','FAIstate'],
2413                 filter => $fai_filter,
2414         );
2415         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2417         if($mesg->{'resultCode'} == 0 &&
2418                 $mesg->count != 0) {
2419                 foreach my $entry (@{$mesg->{entries}}) {
2420                         if($entry->exists('cn')) {
2421                                 my $tmp_dn= $entry->dn();
2423                                 # Skip classname and ou dn parts for class
2424                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2426                                 # Skip classes without releases
2427                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2428                                         next;
2429                                 }
2431                                 my $tmp_cn= $entry->get_value('cn');
2432                                 my $tmp_state= $entry->get_value('FAIstate');
2434                                 my $tmp_type;
2435                                 # Get FAI type
2436                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2437                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2438                                                 $tmp_type= $oclass;
2439                                                 last;
2440                                         }
2441                                 }
2443                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2444                                         # A Subrelease
2445                                         my @sub_releases = split(/,/, $tmp_release);
2447                                         # Walk through subreleases and build hash tree
2448                                         my $hash;
2449                                         while(my $tmp_sub_release = pop @sub_releases) {
2450                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2451                                         }
2452                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2453                                 } else {
2454                                         # A branch, no subrelease
2455                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2456                                 }
2457                         } elsif (!$entry->exists('cn')) {
2458                                 my $tmp_dn= $entry->dn();
2459                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2461                                 # Skip classes without releases
2462                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2463                                         next;
2464                                 }
2466                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2467                                         # A Subrelease
2468                                         my @sub_releases= split(/,/, $tmp_release);
2470                                         # Walk through subreleases and build hash tree
2471                                         my $hash;
2472                                         while(my $tmp_sub_release = pop @sub_releases) {
2473                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2474                                         }
2475                                         # Remove the last two characters
2476                                         chop($hash);
2477                                         chop($hash);
2479                                         eval('$fai_classes->'.$hash.'= {}');
2480                                 } else {
2481                                         # A branch, no subrelease
2482                                         if(!exists($fai_classes->{$tmp_release})) {
2483                                                 $fai_classes->{$tmp_release} = {};
2484                                         }
2485                                 }
2486                         }
2487                 }
2489                 # The hash is complete, now we can honor the copy-on-write based missing entries
2490                 foreach my $release (keys %$fai_classes) {
2491                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2492                 }
2493         }
2494         return $result;
2497 sub apply_fai_inheritance {
2498        my $fai_classes = shift || return {};
2499        my $tmp_classes;
2501        # Get the classes from the branch
2502        foreach my $class (keys %{$fai_classes}) {
2503                # Skip subreleases
2504                if($class =~ /^ou=.*$/) {
2505                        next;
2506                } else {
2507                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2508                }
2509        }
2511        # Apply to each subrelease
2512        foreach my $subrelease (keys %{$fai_classes}) {
2513                if($subrelease =~ /ou=/) {
2514                        foreach my $tmp_class (keys %{$tmp_classes}) {
2515                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2516                                        $fai_classes->{$subrelease}->{$tmp_class} =
2517                                        deep_copy($tmp_classes->{$tmp_class});
2518                                } else {
2519                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2520                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2521                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2522                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2523                                                }
2524                                        }
2525                                }
2526                        }
2527                }
2528        }
2530        # Find subreleases in deeper levels
2531        foreach my $subrelease (keys %{$fai_classes}) {
2532                if($subrelease =~ /ou=/) {
2533                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2534                                if($subsubrelease =~ /ou=/) {
2535                                        apply_fai_inheritance($fai_classes->{$subrelease});
2536                                }
2537                        }
2538                }
2539        }
2541        return $fai_classes;
2544 sub get_fai_release_entries {
2545         my $tmp_classes = shift || return;
2546         my $parent = shift || "";
2547         my @result = shift || ();
2549         foreach my $entry (keys %{$tmp_classes}) {
2550                 if(defined($entry)) {
2551                         if($entry =~ /^ou=.*$/) {
2552                                 my $release_name = $entry;
2553                                 $release_name =~ s/ou=//g;
2554                                 if(length($parent)>0) {
2555                                         $release_name = $parent."/".$release_name;
2556                                 }
2557                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2558                                 foreach my $bufentry(@bufentries) {
2559                                         push @result, $bufentry;
2560                                 }
2561                         } else {
2562                                 my @types = get_fai_types($tmp_classes->{$entry});
2563                                 foreach my $type (@types) {
2564                                         push @result, 
2565                                         {
2566                                                 'class' => $entry,
2567                                                 'type' => $type->{'type'},
2568                                                 'release' => $parent,
2569                                                 'state' => $type->{'state'},
2570                                         };
2571                                 }
2572                         }
2573                 }
2574         }
2576         return @result;
2579 sub deep_copy {
2580         my $this = shift;
2581         if (not ref $this) {
2582                 $this;
2583         } elsif (ref $this eq "ARRAY") {
2584                 [map deep_copy($_), @$this];
2585         } elsif (ref $this eq "HASH") {
2586                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2587         } else { die "what type is $_?" }
2591 sub session_run_result {
2592     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2593     $kernel->sig(CHLD => "child_reap");
2596 sub session_run_debug {
2597     my $result = $_[ARG0];
2598     print STDERR "$result\n";
2601 sub session_run_done {
2602     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2603     delete $heap->{task}->{$task_id};
2607 sub create_sources_list {
2608         my $session_id = shift;
2609         my $ldap_handle = &main::get_ldap_handle;
2610         my $result="/tmp/gosa_si_tmp_sources_list";
2612         # Remove old file
2613         if(stat($result)) {
2614                 unlink($result);
2615                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2616         }
2618         my $fh;
2619         open($fh, ">$result");
2620         if (not defined $fh) {
2621                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2622                 return undef;
2623         }
2624         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2625                 my $mesg=$ldap_handle->search(
2626                         base    => $main::ldap_server_dn,
2627                         scope   => 'base',
2628                         attrs   => 'FAIrepository',
2629                         filter  => 'objectClass=FAIrepositoryServer'
2630                 );
2631                 if($mesg->count) {
2632                         foreach my $entry(@{$mesg->{'entries'}}) {
2633                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2634                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2635                                         my $line = "deb $server $release";
2636                                         $sections =~ s/,/ /g;
2637                                         $line.= " $sections";
2638                                         print $fh $line."\n";
2639                                 }
2640                         }
2641                 }
2642         } else {
2643                 if (defined $main::ldap_server_dn){
2644                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2645                 } else {
2646                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2647                 }
2648         }
2649         close($fh);
2651         return $result;
2655 sub run_create_packages_list_db {
2656     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2657         my $session_id = $session->ID;
2659         my $task = POE::Wheel::Run->new(
2660                                         Priority => +20,
2661                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2662                                         StdoutEvent  => "session_run_result",
2663                                         StderrEvent  => "session_run_debug",
2664                                         CloseEvent   => "session_run_done",
2665                                         );
2666         $heap->{task}->{ $task->ID } = $task;
2670 sub create_packages_list_db {
2671         my ($ldap_handle, $sources_file, $session_id) = @_;
2672         
2673         # it should not be possible to trigger a recreation of packages_list_db
2674         # while packages_list_db is under construction, so set flag packages_list_under_construction
2675         # which is tested befor recreation can be started
2676         if (-r $packages_list_under_construction) {
2677                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2678                 return;
2679         } else {
2680                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2681                 # set packages_list_under_construction to true
2682                 system("touch $packages_list_under_construction");
2683                 @packages_list_statements=();
2684         }
2686         if (not defined $session_id) { $session_id = 0; }
2687         if (not defined $ldap_handle) { 
2688                 $ldap_handle= &get_ldap_handle();
2690                 if (not defined $ldap_handle) {
2691                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2692                         unlink($packages_list_under_construction);
2693                         return;
2694                 }
2695         }
2696         if (not defined $sources_file) { 
2697                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2698                 $sources_file = &create_sources_list($session_id);
2699         }
2701         if (not defined $sources_file) {
2702                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2703                 unlink($packages_list_under_construction);
2704                 return;
2705         }
2707         my $line;
2709         open(CONFIG, "<$sources_file") or do {
2710                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2711                 unlink($packages_list_under_construction);
2712                 return;
2713         };
2715         # Read lines
2716         while ($line = <CONFIG>){
2717                 # Unify
2718                 chop($line);
2719                 $line =~ s/^\s+//;
2720                 $line =~ s/^\s+/ /;
2722                 # Strip comments
2723                 $line =~ s/#.*$//g;
2725                 # Skip empty lines
2726                 if ($line =~ /^\s*$/){
2727                         next;
2728                 }
2730                 # Interpret deb line
2731                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2732                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2733                         my $section;
2734                         foreach $section (split(' ', $sections)){
2735                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2736                         }
2737                 }
2738         }
2740         close (CONFIG);
2743         find(\&cleanup_and_extract, keys( %repo_dirs ));
2744         &main::strip_packages_list_statements();
2745         unshift @packages_list_statements, "VACUUM";
2746         $packages_list_db->exec_statementlist(\@packages_list_statements);
2747         unlink($packages_list_under_construction);
2748         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2749         return;
2752 # This function should do some intensive task to minimize the db-traffic
2753 sub strip_packages_list_statements {
2754     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2755         my @new_statement_list=();
2756         my $hash;
2757         my $insert_hash;
2758         my $update_hash;
2759         my $delete_hash;
2760         my $local_timestamp=get_time();
2762         foreach my $existing_entry (@existing_entries) {
2763                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2764         }
2766         foreach my $statement (@packages_list_statements) {
2767                 if($statement =~ /^INSERT/i) {
2768                         # Assign the values from the insert statement
2769                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2770                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2771                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2772                                 # If section or description has changed, update the DB
2773                                 if( 
2774                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2775                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2776                                 ) {
2777                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2778                                 }
2779                         } else {
2780                                 # Insert a non-existing entry to db
2781                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2782                         }
2783                 } elsif ($statement =~ /^UPDATE/i) {
2784                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2785                         /^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;
2786                         foreach my $distribution (keys %{$hash}) {
2787                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2788                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2789                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2790                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2791                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2792                                                 my $section;
2793                                                 my $description;
2794                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2795                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2796                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2797                                                 }
2798                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2799                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2800                                                 }
2801                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2802                                         }
2803                                 }
2804                         }
2805                 }
2806         }
2808         # TODO: Check for orphaned entries
2810         # unroll the insert_hash
2811         foreach my $distribution (keys %{$insert_hash}) {
2812                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2813                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2814                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2815                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2816                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2817                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2818                                 ."'$local_timestamp')";
2819                         }
2820                 }
2821         }
2823         # unroll the update hash
2824         foreach my $distribution (keys %{$update_hash}) {
2825                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2826                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2827                                 my $set = "";
2828                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2829                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2830                                 }
2831                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2832                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2833                                 }
2834                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2835                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2836                                 }
2837                                 if(defined($set) and length($set) > 0) {
2838                                         $set .= "timestamp = '$local_timestamp'";
2839                                 } else {
2840                                         next;
2841                                 }
2842                                 push @new_statement_list, 
2843                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2844                                         ." distribution = '$distribution'"
2845                                         ." AND package = '$package'"
2846                                         ." AND version = '$version'";
2847                         }
2848                 }
2849         }
2851         @packages_list_statements = @new_statement_list;
2855 sub parse_package_info {
2856     my ($baseurl, $dist, $section, $session_id)= @_;
2857     my ($package);
2858     if (not defined $session_id) { $session_id = 0; }
2859     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2860     $repo_dirs{ "${repo_path}/pool" } = 1;
2862     foreach $package ("Packages.gz"){
2863         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2864         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2865         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2866     }
2867     
2871 sub get_package {
2872     my ($url, $dest, $session_id)= @_;
2873     if (not defined $session_id) { $session_id = 0; }
2875     my $tpath = dirname($dest);
2876     -d "$tpath" || mkpath "$tpath";
2878     # This is ugly, but I've no time to take a look at "how it works in perl"
2879     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2880         system("gunzip -cd '$dest' > '$dest.in'");
2881         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2882         unlink($dest);
2883         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2884     } else {
2885         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2886     }
2887     return 0;
2891 sub parse_package {
2892     my ($path, $dist, $srv_path, $session_id)= @_;
2893     if (not defined $session_id) { $session_id = 0;}
2894     my ($package, $version, $section, $description);
2895     my $PACKAGES;
2896     my $timestamp = &get_time();
2898     if(not stat("$path.in")) {
2899         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2900         return;
2901     }
2903     open($PACKAGES, "<$path.in");
2904     if(not defined($PACKAGES)) {
2905         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2906         return;
2907     }
2909     # Read lines
2910     while (<$PACKAGES>){
2911         my $line = $_;
2912         # Unify
2913         chop($line);
2915         # Use empty lines as a trigger
2916         if ($line =~ /^\s*$/){
2917             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2918             push(@packages_list_statements, $sql);
2919             $package = "none";
2920             $version = "none";
2921             $section = "none";
2922             $description = "none"; 
2923             next;
2924         }
2926         # Trigger for package name
2927         if ($line =~ /^Package:\s/){
2928             ($package)= ($line =~ /^Package: (.*)$/);
2929             next;
2930         }
2932         # Trigger for version
2933         if ($line =~ /^Version:\s/){
2934             ($version)= ($line =~ /^Version: (.*)$/);
2935             next;
2936         }
2938         # Trigger for description
2939         if ($line =~ /^Description:\s/){
2940             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2941             next;
2942         }
2944         # Trigger for section
2945         if ($line =~ /^Section:\s/){
2946             ($section)= ($line =~ /^Section: (.*)$/);
2947             next;
2948         }
2950         # Trigger for filename
2951         if ($line =~ /^Filename:\s/){
2952             my ($filename) = ($line =~ /^Filename: (.*)$/);
2953             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2954             next;
2955         }
2956     }
2958     close( $PACKAGES );
2959     unlink( "$path.in" );
2963 sub store_fileinfo {
2964     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2966     my %fileinfo = (
2967         'package' => $package,
2968         'dist' => $dist,
2969         'version' => $vers,
2970     );
2972     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2976 sub cleanup_and_extract {
2977     my $fileinfo = $repo_files{ $File::Find::name };
2979     if( defined $fileinfo ) {
2981         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2982         my $sql;
2983         my $package = $fileinfo->{ 'package' };
2984         my $newver = $fileinfo->{ 'version' };
2986         mkpath($dir);
2987         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2989                 if( -f "$dir/DEBIAN/templates" ) {
2991                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
2993                         my $tmpl= "";
2994                         {
2995                                 local $/=undef;
2996                                 open FILE, "$dir/DEBIAN/templates";
2997                                 $tmpl = &encode_base64(<FILE>);
2998                                 close FILE;
2999                         }
3000                         rmtree("$dir/DEBIAN/templates");
3002                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3003                 push @packages_list_statements, $sql;
3004                 }
3005     }
3007     return;
3011 sub register_at_foreign_servers {   
3012     my ($kernel) = $_[KERNEL];
3014     # hole alle bekannten server aus known_server_db
3015     my $server_sql = "SELECT * FROM $known_server_tn";
3016     my $server_res = $known_server_db->exec_statement($server_sql);
3018     # no entries in known_server_db
3019     if (not ref(@$server_res[0]) eq "ARRAY") { 
3020         # TODO
3021     }
3023     # detect already connected clients
3024     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3025     my $client_res = $known_clients_db->exec_statement($client_sql);
3027     # send my server details to all other gosa-si-server within the network
3028     foreach my $hit (@$server_res) {
3029         my $hostname = @$hit[0];
3030         my $hostkey = &create_passwd;
3032         # add already connected clients to registration message 
3033         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3034         &add_content2xml_hash($myhash, 'key', $hostkey);
3035         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3037         # add locally loaded gosa-si modules to registration message
3038         my $loaded_modules = {};
3039         while (my ($package, $pck_info) = each %$known_modules) {
3040             foreach my $act_module (keys(%{@$pck_info[2]})) {
3041                 $loaded_modules->{$act_module} = ""; 
3042             }
3043         }
3044         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3046         # add macaddress to registration message
3047         my ($host_ip, $host_port) = split(/:/, $hostname);
3048         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3049         my $network_interface= &get_interface_for_ip($local_ip);
3050         my $host_mac = &get_mac_for_interface($network_interface);
3051         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3052         
3053         # build registration message and send it
3054         my $foreign_server_msg = &create_xml_string($myhash);
3055         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3056     }
3057     
3058     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3059     return;
3063 #==== MAIN = main ==============================================================
3064 #  parse commandline options
3065 Getopt::Long::Configure( "bundling" );
3066 GetOptions("h|help" => \&usage,
3067         "c|config=s" => \$cfg_file,
3068         "f|foreground" => \$foreground,
3069         "v|verbose+" => \$verbose,
3070         "no-arp+" => \$no_arp,
3071            );
3073 #  read and set config parameters
3074 &check_cmdline_param ;
3075 &read_configfile($cfg_file, %cfg_defaults);
3076 &check_pid;
3078 $SIG{CHLD} = 'IGNORE';
3080 # forward error messages to logfile
3081 if( ! $foreground ) {
3082   open( STDIN,  '+>/dev/null' );
3083   open( STDOUT, '+>&STDIN'    );
3084   open( STDERR, '+>&STDIN'    );
3087 # Just fork, if we are not in foreground mode
3088 if( ! $foreground ) { 
3089     chdir '/'                 or die "Can't chdir to /: $!";
3090     $pid = fork;
3091     setsid                    or die "Can't start a new session: $!";
3092     umask 0;
3093 } else { 
3094     $pid = $$; 
3097 # Do something useful - put our PID into the pid_file
3098 if( 0 != $pid ) {
3099     open( LOCK_FILE, ">$pid_file" );
3100     print LOCK_FILE "$pid\n";
3101     close( LOCK_FILE );
3102     if( !$foreground ) { 
3103         exit( 0 ) 
3104     };
3107 # parse head url and revision from svn
3108 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3109 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3110 $server_headURL = defined $1 ? $1 : 'unknown' ;
3111 $server_revision = defined $2 ? $2 : 'unknown' ;
3112 if ($server_headURL =~ /\/tag\// || 
3113         $server_headURL =~ /\/branches\// ) {
3114     $server_status = "stable"; 
3115 } else {
3116     $server_status = "developmental" ;
3120 daemon_log(" ", 1);
3121 daemon_log("$0 started!", 1);
3122 daemon_log("status: $server_status", 1);
3123 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3125 # connect to incoming_db
3126 unlink($incoming_file_name);
3127 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3128 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3130 # connect to gosa-si job queue
3131 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3132 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3134 # connect to known_clients_db
3135 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3136 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3138 # connect to foreign_clients_db
3139 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3140 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3142 # connect to known_server_db
3143 unlink($known_server_file_name);
3144 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3145 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3147 # connect to login_usr_db
3148 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3149 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3151 # connect to fai_server_db and fai_release_db
3152 unlink($fai_server_file_name);
3153 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3154 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3156 unlink($fai_release_file_name);
3157 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3158 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3160 # connect to packages_list_db
3161 #unlink($packages_list_file_name);
3162 unlink($packages_list_under_construction);
3163 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3164 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3166 # connect to messaging_db
3167 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3168 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3171 # create xml object used for en/decrypting
3172 $xml = new XML::Simple();
3175 # foreign servers 
3176 my @foreign_server_list;
3178 # add foreign server from cfg file
3179 if ($foreign_server_string ne "") {
3180     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3181     foreach my $foreign_server (@cfg_foreign_server_list) {
3182         push(@foreign_server_list, $foreign_server);
3183     }
3186 # add foreign server from dns
3187 my @tmp_servers;
3188 if ( !$server_domain) {
3189     # Try our DNS Searchlist
3190     for my $domain(get_dns_domains()) {
3191         chomp($domain);
3192         my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3193         if(@$tmp_domains) {
3194             for my $tmp_server(@$tmp_domains) {
3195                 push @tmp_servers, $tmp_server;
3196             }
3197         }
3198     }
3199     if(@tmp_servers && length(@tmp_servers)==0) {
3200         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3201     }
3202 } else {
3203     @tmp_servers = &get_server_addresses($server_domain);
3204     if( 0 == @tmp_servers ) {
3205         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3206     }
3208 foreach my $server (@tmp_servers) { 
3209     unshift(@foreign_server_list, $server); 
3211 # eliminate duplicate entries
3212 @foreign_server_list = &del_doubles(@foreign_server_list);
3213 my $all_foreign_server = join(", ", @foreign_server_list);
3214 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3216 # add all found foreign servers to known_server
3217 my $act_timestamp = &get_time();
3218 foreach my $foreign_server (@foreign_server_list) {
3220         # do not add myself to known_server_db
3221         if (&is_local($foreign_server)) { next; }
3222         ######################################
3224     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3225             primkey=>['hostname'],
3226             hostname=>$foreign_server,
3227             macaddress=>"",
3228             status=>'not_jet_registered',
3229             hostkey=>"none",
3230             loaded_modules => "none", 
3231             timestamp=>$act_timestamp,
3232             } );
3236 # Import all modules
3237 &import_modules;
3239 # Check wether all modules are gosa-si valid passwd check
3240 &password_check;
3242 # Prepare for using Opsi 
3243 if ($opsi_enabled eq "true") {
3244     use JSON::RPC::Client;
3245     use XML::Quote qw(:all);
3246     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3247     $opsi_client = new JSON::RPC::Client;
3251 POE::Component::Server::TCP->new(
3252     Alias => "TCP_SERVER",
3253         Port => $server_port,
3254         ClientInput => sub {
3255         my ($kernel, $input) = @_[KERNEL, ARG0];
3256         push(@tasks, $input);
3257         push(@msgs_to_decrypt, $input);
3258         $kernel->yield("msg_to_decrypt");
3259         },
3260     InlineStates => {
3261         msg_to_decrypt => \&msg_to_decrypt,
3262         next_task => \&next_task,
3263         task_result => \&handle_task_result,
3264         task_done   => \&handle_task_done,
3265         task_debug  => \&handle_task_debug,
3266         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3267     }
3268 );
3270 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3272 # create session for repeatedly checking the job queue for jobs
3273 POE::Session->create(
3274         inline_states => {
3275                 _start => \&session_start,
3276         register_at_foreign_servers => \&register_at_foreign_servers,
3277         sig_handler => \&sig_handler,
3278         next_task => \&next_task,
3279         task_result => \&handle_task_result,
3280         task_done   => \&handle_task_done,
3281         task_debug  => \&handle_task_debug,
3282         watch_for_next_tasks => \&watch_for_next_tasks,
3283         watch_for_new_messages => \&watch_for_new_messages,
3284         watch_for_delivery_messages => \&watch_for_delivery_messages,
3285         watch_for_done_messages => \&watch_for_done_messages,
3286                 watch_for_new_jobs => \&watch_for_new_jobs,
3287         watch_for_modified_jobs => \&watch_for_modified_jobs,
3288         watch_for_done_jobs => \&watch_for_done_jobs,
3289         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3290         watch_for_old_known_clients => \&watch_for_old_known_clients,
3291         create_packages_list_db => \&run_create_packages_list_db,
3292         create_fai_server_db => \&run_create_fai_server_db,
3293         create_fai_release_db => \&run_create_fai_release_db,
3294                 recreate_packages_db => \&run_recreate_packages_db,
3295         session_run_result => \&session_run_result,
3296         session_run_debug => \&session_run_debug,
3297         session_run_done => \&session_run_done,
3298         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3299         }
3300 );
3303 POE::Kernel->run();
3304 exit;