Code

c1fe2d63f0c36e389eab2db1954d5fa3d4d7bf9e
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($procid, $pid);
75 my ($arp_fifo);
76 my ($xml);
77 my $sources_list;
78 my $max_clients;
79 my %repo_files=();
80 my $repo_path;
81 my %repo_dirs=();
82 # variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $ClientPackages_key, 
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_timeout,
87     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89     $arp_enabled, $arp_interface,
90     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91 );
93 # additional variable which should be globaly accessable
94 our $server_address;
95 our $server_mac_address;
96 our $gosa_address;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 our $known_modules;
104 # specifies the verbosity of the daemon_log
105 $verbose = 0 ;
107 # if foreground is not null, script will be not forked to background
108 $foreground = 0 ;
110 # specifies the timeout seconds while checking the online status of a registrating client
111 $ping_timeout = 5;
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
123         "timestamp DEFAULT 'none'", 
124         "headertag DEFAULT 'none'",
125                 "targettag DEFAULT 'none'",
126         "xmlmessage DEFAULT 'none'",
127         "module DEFAULT 'none'",
128         "sessionid DEFAULT '0'",
129         );
131 # holds all gosa jobs
132 our $job_db;
133 our $job_queue_tn = 'jobs';
134 my $job_queue_file_name;
135 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
136                 "timestamp DEFAULT 'none'", 
137                 "status DEFAULT 'none'", 
138                 "result DEFAULT 'none'", 
139                 "progress DEFAULT 'none'", 
140         "headertag DEFAULT 'none'", 
141                 "targettag DEFAULT 'none'", 
142                 "xmlmessage DEFAULT 'none'", 
143                 "macaddress DEFAULT 'none'",
144                 "plainname DEFAULT 'none'",
145         "siserver DEFAULT 'none'",
146         "modified DEFAULT '0'",
147                 );
149 # holds all other gosa-si-server
150 our $known_server_db;
151 our $known_server_tn = "known_server";
152 my $known_server_file_name;
153 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
155 # holds all registrated clients
156 our $known_clients_db;
157 our $known_clients_tn = "known_clients";
158 my $known_clients_file_name;
159 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
161 # holds all registered clients at a foreign server
162 our $foreign_clients_db;
163 our $foreign_clients_tn = "foreign_clients"; 
164 my $foreign_clients_file_name;
165 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
167 # holds all logged in user at each client 
168 our $login_users_db;
169 our $login_users_tn = "login_users";
170 my $login_users_file_name;
171 my @login_users_col_names = ("client", "user", "timestamp");
173 # holds all fai server, the debian release and tag
174 our $fai_server_db;
175 our $fai_server_tn = "fai_server"; 
176 my $fai_server_file_name;
177 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
179 our $fai_release_db;
180 our $fai_release_tn = "fai_release"; 
181 my $fai_release_file_name;
182 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
184 # holds all packages available from different repositories
185 our $packages_list_db;
186 our $packages_list_tn = "packages_list";
187 my $packages_list_file_name;
188 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
189 my $outdir = "/tmp/packages_list_db";
190 my $arch = "i386"; 
192 # holds all messages which should be delivered to a user
193 our $messaging_db;
194 our $messaging_tn = "messaging"; 
195 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
196         "flag", "direction", "delivery_time", "message", "timestamp" );
197 my $messaging_file_name;
199 # path to directory to store client install log files
200 our $client_fai_log_dir = "/var/log/fai"; 
202 # queue which stores taskes until one of the $max_children children are ready to process the task
203 my @tasks = qw();
204 my @msgs_to_decrypt = qw();
205 my $max_children = 2;
208 # loop delay for job queue to look for opsi jobs
209 my $job_queue_opsi_delay = 10;
210 our $opsi_client;
211 our $opsi_url;
212  
215 %cfg_defaults = (
216 "general" => {
217     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
218     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
219     },
220 "server" => {
221     "ip" => [\$server_ip, "0.0.0.0"],
222     "port" => [\$server_port, "20081"],
223     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
224     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
225     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
226     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
227     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
228     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
229     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
230     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
231     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
232     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
233     "repo-path"            => [\$repo_path, '/srv/www/repository'],
234     "ldap-uri"             => [\$ldap_uri, ""],
235     "ldap-base"            => [\$ldap_base, ""],
236     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
237     "ldap-admin-password"  => [\$ldap_admin_password, ""],
238     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
239     "max-clients"          => [\$max_clients, 10],
240     "wol-password"           => [\$wake_on_lan_passwd, ""],
241     },
242 "GOsaPackages" => {
243     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
244     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
245     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
246     "key" => [\$GosaPackages_key, "none"],
247     },
248 "ClientPackages" => {
249     "key" => [\$ClientPackages_key, "none"],
250     },
251 "ServerPackages"=> {
252     "address"      => [\$foreign_server_string, ""],
253     "domain"  => [\$server_domain, ""],
254     "key"     => [\$ServerPackages_key, "none"],
255     "key-lifetime" => [\$foreign_servers_register_delay, 120],
256     "job-synchronization-enabled" => [\$job_synchronization, "true"],
257     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
258     },
259 "ArpHandler" => {
260     "enabled"   => [\$arp_enabled, "true"],
261     "interface" => [\$arp_interface, "all"],
262         },
263 "Opsi" => {
264     "enabled"  => [\$opsi_enabled, "false"], 
265     "server"   => [\$opsi_server, "localhost"],
266     "admin"    => [\$opsi_admin, "opsi-admin"],
267     "password" => [\$opsi_password, "secret"],
268    },
270 );
273 #===  FUNCTION  ================================================================
274 #         NAME:  usage
275 #   PARAMETERS:  nothing
276 #      RETURNS:  nothing
277 #  DESCRIPTION:  print out usage text to STDERR
278 #===============================================================================
279 sub usage {
280     print STDERR << "EOF" ;
281 usage: $prg [-hvf] [-c config]
283            -h        : this (help) message
284            -c <file> : config file
285            -f        : foreground, process will not be forked to background
286            -v        : be verbose (multiple to increase verbosity)
287            -no-arp   : starts $prg without connection to arp module
288  
289 EOF
290     print "\n" ;
294 #===  FUNCTION  ================================================================
295 #         NAME:  logging
296 #   PARAMETERS:  level - string - default 'info'
297 #                msg - string -
298 #                facility - string - default 'LOG_DAEMON'
299 #      RETURNS:  nothing
300 #  DESCRIPTION:  function for logging
301 #===============================================================================
302 sub daemon_log {
303     # log into log_file
304     my( $msg, $level ) = @_;
305     if(not defined $msg) { return }
306     if(not defined $level) { $level = 1 }
307     if(defined $log_file){
308         open(LOG_HANDLE, ">>$log_file");
309         chmod 0600, $log_file;
310         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
311             print STDERR "cannot open $log_file: $!";
312             return 
313         }
314         chomp($msg);
315         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
316         if($level <= $verbose){
317             my ($seconds, $minutes, $hours, $monthday, $month,
318                     $year, $weekday, $yearday, $sommertime) = localtime(time);
319             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
320             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
321             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
322             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
323             $month = $monthnames[$month];
324             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
325             $year+=1900;
326             my $name = $prg;
328             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
329             print LOG_HANDLE $log_msg;
330             if( $foreground ) { 
331                 print STDERR $log_msg;
332             }
333         }
334         close( LOG_HANDLE );
335     }
339 #===  FUNCTION  ================================================================
340 #         NAME:  check_cmdline_param
341 #   PARAMETERS:  nothing
342 #      RETURNS:  nothing
343 #  DESCRIPTION:  validates commandline parameter
344 #===============================================================================
345 sub check_cmdline_param () {
346     my $err_config;
347     my $err_counter = 0;
348         if(not defined($cfg_file)) {
349                 $cfg_file = "/etc/gosa-si/server.conf";
350                 if(! -r $cfg_file) {
351                         $err_config = "please specify a config file";
352                         $err_counter += 1;
353                 }
354     }
355     if( $err_counter > 0 ) {
356         &usage( "", 1 );
357         if( defined( $err_config)) { print STDERR "$err_config\n"}
358         print STDERR "\n";
359         exit( -1 );
360     }
364 #===  FUNCTION  ================================================================
365 #         NAME:  check_pid
366 #   PARAMETERS:  nothing
367 #      RETURNS:  nothing
368 #  DESCRIPTION:  handels pid processing
369 #===============================================================================
370 sub check_pid {
371     $pid = -1;
372     # Check, if we are already running
373     if( open(LOCK_FILE, "<$pid_file") ) {
374         $pid = <LOCK_FILE>;
375         if( defined $pid ) {
376             chomp( $pid );
377             if( -f "/proc/$pid/stat" ) {
378                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
379                 if( $stat ) {
380                                         daemon_log("ERROR: Already running",1);
381                     close( LOCK_FILE );
382                     exit -1;
383                 }
384             }
385         }
386         close( LOCK_FILE );
387         unlink( $pid_file );
388     }
390     # create a syslog msg if it is not to possible to open PID file
391     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
392         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
393         if (open(LOCK_FILE, '<', $pid_file)
394                 && ($pid = <LOCK_FILE>))
395         {
396             chomp($pid);
397             $msg .= "(PID $pid)\n";
398         } else {
399             $msg .= "(unable to read PID)\n";
400         }
401         if( ! ($foreground) ) {
402             openlog( $0, "cons,pid", "daemon" );
403             syslog( "warning", $msg );
404             closelog();
405         }
406         else {
407             print( STDERR " $msg " );
408         }
409         exit( -1 );
410     }
413 #===  FUNCTION  ================================================================
414 #         NAME:  import_modules
415 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
416 #                are stored
417 #      RETURNS:  nothing
418 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
419 #                state is on is imported by "require 'file';"
420 #===============================================================================
421 sub import_modules {
422     daemon_log(" ", 1);
424     if (not -e $modules_path) {
425         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
426     }
428     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
429     while (defined (my $file = readdir (DIR))) {
430         if (not $file =~ /(\S*?).pm$/) {
431             next;
432         }
433                 my $mod_name = $1;
435         # ArpHandler switch
436         if( $file =~ /ArpHandler.pm/ ) {
437             if( $arp_enabled eq "false" ) { next; }
438         }
439         
440         eval { require $file; };
441         if ($@) {
442             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
443             daemon_log("$@", 5);
444                 } else {
445                         my $info = eval($mod_name.'::get_module_info()');
446                         # Only load module if get_module_info() returns a non-null object
447                         if( $info ) {
448                                 my ($input_address, $input_key, $event_hash) = @{$info};
449                                 $known_modules->{$mod_name} = $info;
450                                 daemon_log("0 INFO: module $mod_name loaded", 5);
451                         }
452                 }
453     }   
455     close (DIR);
458 #===  FUNCTION  ================================================================
459 #         NAME:  password_check
460 #   PARAMETERS:  nothing
461 #      RETURNS:  nothing
462 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
463 #                the same password
464 #===============================================================================
465 sub password_check {
466     my $passwd_hash = {};
467     while (my ($mod_name, $mod_info) = each %$known_modules) {
468         my $mod_passwd = @$mod_info[1];
469         if (not defined $mod_passwd) { next; }
470         if (not exists $passwd_hash->{$mod_passwd}) {
471             $passwd_hash->{$mod_passwd} = $mod_name;
473         # escalates critical error
474         } else {
475             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
476             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
477             exit( -1 );
478         }
479     }
484 #===  FUNCTION  ================================================================
485 #         NAME:  sig_int_handler
486 #   PARAMETERS:  signal - string - signal arose from system
487 #      RETURNS:  nothing
488 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
489 #===============================================================================
490 sub sig_int_handler {
491     my ($signal) = @_;
493 #       if (defined($ldap_handle)) {
494 #               $ldap_handle->disconnect;
495 #       }
496     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
497     
499     daemon_log("shutting down gosa-si-server", 1);
500     system("kill `ps -C gosa-si-server -o pid=`");
502 $SIG{INT} = \&sig_int_handler;
505 sub check_key_and_xml_validity {
506     my ($crypted_msg, $module_key, $session_id) = @_;
507     my $msg;
508     my $msg_hash;
509     my $error_string;
510     eval{
511         $msg = &decrypt_msg($crypted_msg, $module_key);
513         if ($msg =~ /<xml>/i){
514             $msg =~ s/\s+/ /g;  # just for better daemon_log
515             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
516             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
518             ##############
519             # check header
520             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
521             my $header_l = $msg_hash->{'header'};
522             if( 1 > @{$header_l} ) { die 'empty header tag'; }
523             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
524             my $header = @{$header_l}[0];
525             if( 0 == length $header) { die 'empty string in header tag'; }
527             ##############
528             # check source
529             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
530             my $source_l = $msg_hash->{'source'};
531             if( 1 > @{$source_l} ) { die 'empty source tag'; }
532             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
533             my $source = @{$source_l}[0];
534             if( 0 == length $source) { die 'source error'; }
536             ##############
537             # check target
538             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
539             my $target_l = $msg_hash->{'target'};
540             if( 1 > @{$target_l} ) { die 'empty target tag'; }
541         }
542     };
543     if($@) {
544         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
545         $msg = undef;
546         $msg_hash = undef;
547     }
549     return ($msg, $msg_hash);
553 sub check_outgoing_xml_validity {
554     my ($msg, $session_id) = @_;
556     my $msg_hash;
557     eval{
558         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
560         ##############
561         # check header
562         my $header_l = $msg_hash->{'header'};
563         if( 1 != @{$header_l} ) {
564             die 'no or more than one headers specified';
565         }
566         my $header = @{$header_l}[0];
567         if( 0 == length $header) {
568             die 'header has length 0';
569         }
571         ##############
572         # check source
573         my $source_l = $msg_hash->{'source'};
574         if( 1 != @{$source_l} ) {
575             die 'no or more than 1 sources specified';
576         }
577         my $source = @{$source_l}[0];
578         if( 0 == length $source) {
579             die 'source has length 0';
580         }
581         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582                 $source =~ /^GOSA$/i ) {
583             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
584         }
585         
586         ##############
587         # check target  
588         my $target_l = $msg_hash->{'target'};
589         if( 0 == @{$target_l} ) {
590             die "no targets specified";
591         }
592         foreach my $target (@$target_l) {
593             if( 0 == length $target) {
594                 die "target has length 0";
595             }
596             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
597                     $target =~ /^GOSA$/i ||
598                     $target =~ /^\*$/ ||
599                     $target =~ /KNOWN_SERVER/i ||
600                     $target =~ /JOBDB/i ||
601                     $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 ){
602                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
603             }
604         }
605     };
606     if($@) {
607         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
608         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
609         $msg_hash = undef;
610     }
612     return ($msg_hash);
616 sub input_from_known_server {
617     my ($input, $remote_ip, $session_id) = @_ ;  
618     my ($msg, $msg_hash, $module);
620     my $sql_statement= "SELECT * FROM known_server";
621     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
623     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
624         my $host_name = $hit->{hostname};
625         if( not $host_name =~ "^$remote_ip") {
626             next;
627         }
628         my $host_key = $hit->{hostkey};
629         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
630         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
632         # check if module can open msg envelope with module key
633         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
634         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
635             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
636             daemon_log("$@", 8);
637             next;
638         }
639         else {
640             $msg = $tmp_msg;
641             $msg_hash = $tmp_msg_hash;
642             $module = "ServerPackages";
643             last;
644         }
645     }
647     if( (!$msg) || (!$msg_hash) || (!$module) ) {
648         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
649     }
650   
651     return ($msg, $msg_hash, $module);
655 sub input_from_known_client {
656     my ($input, $remote_ip, $session_id) = @_ ;  
657     my ($msg, $msg_hash, $module);
659     my $sql_statement= "SELECT * FROM known_clients";
660     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
661     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
662         my $host_name = $hit->{hostname};
663         if( not $host_name =~ /^$remote_ip:\d*$/) {
664                 next;
665                 }
666         my $host_key = $hit->{hostkey};
667         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
668         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
670         # check if module can open msg envelope with module key
671         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
673         if( (!$msg) || (!$msg_hash) ) {
674             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
675             &daemon_log("$@", 8);
676             next;
677         }
678         else {
679             $module = "ClientPackages";
680             last;
681         }
682     }
684     if( (!$msg) || (!$msg_hash) || (!$module) ) {
685         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
686     }
688     return ($msg, $msg_hash, $module);
692 sub input_from_unknown_host {
693     no strict "refs";
694     my ($input, $session_id) = @_ ;
695     my ($msg, $msg_hash, $module);
696     my $error_string;
697     
698         my %act_modules = %$known_modules;
699         
700     while( my ($mod, $info) = each(%act_modules)) {
702         # check a key exists for this module
703         my $module_key = ${$mod."_key"};
704         if( not defined $module_key ) {
705             if( $mod eq 'ArpHandler' ) {
706                 next;
707             }
708             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
709             next;
710         }
711         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
713         # check if module can open msg envelope with module key
714         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
715         if( (not defined $msg) || (not defined $msg_hash) ) {
716             next;
717         }
718         else {
719             $module = $mod;
720             last;
721         }
722     }
724     if( (!$msg) || (!$msg_hash) || (!$module)) {
725         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
726     }
728     return ($msg, $msg_hash, $module);
732 sub create_ciphering {
733     my ($passwd) = @_;
734         if((!defined($passwd)) || length($passwd)==0) {
735                 $passwd = "";
736         }
737     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
738     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
739     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
740     $my_cipher->set_iv($iv);
741     return $my_cipher;
745 sub encrypt_msg {
746     my ($msg, $key) = @_;
747     my $my_cipher = &create_ciphering($key);
748     my $len;
749     {
750             use bytes;
751             $len= 16-length($msg)%16;
752     }
753     $msg = "\0"x($len).$msg;
754     $msg = $my_cipher->encrypt($msg);
755     chomp($msg = &encode_base64($msg));
756     # there are no newlines allowed inside msg
757     $msg=~ s/\n//g;
758     return $msg;
762 sub decrypt_msg {
764     my ($msg, $key) = @_ ;
765     $msg = &decode_base64($msg);
766     my $my_cipher = &create_ciphering($key);
767     $msg = $my_cipher->decrypt($msg); 
768     $msg =~ s/\0*//g;
769     return $msg;
773 sub get_encrypt_key {
774     my ($target) = @_ ;
775     my $encrypt_key;
776     my $error = 0;
778     # target can be in known_server
779     if( not defined $encrypt_key ) {
780         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
781         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
782         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
783             my $host_name = $hit->{hostname};
784             if( $host_name ne $target ) {
785                 next;
786             }
787             $encrypt_key = $hit->{hostkey};
788             last;
789         }
790     }
792     # target can be in known_client
793     if( not defined $encrypt_key ) {
794         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
795         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
796         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
797             my $host_name = $hit->{hostname};
798             if( $host_name ne $target ) {
799                 next;
800             }
801             $encrypt_key = $hit->{hostkey};
802             last;
803         }
804     }
806     return $encrypt_key;
810 #===  FUNCTION  ================================================================
811 #         NAME:  open_socket
812 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
813 #                [PeerPort] string necessary if port not appended by PeerAddr
814 #      RETURNS:  socket IO::Socket::INET
815 #  DESCRIPTION:  open a socket to PeerAddr
816 #===============================================================================
817 sub open_socket {
818     my ($PeerAddr, $PeerPort) = @_ ;
819     if(defined($PeerPort)){
820         $PeerAddr = $PeerAddr.":".$PeerPort;
821     }
822     my $socket;
823     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
824             Porto => "tcp",
825             Type => SOCK_STREAM,
826             Timeout => 5,
827             );
828     if(not defined $socket) {
829         return;
830     }
831 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
832     return $socket;
836 #sub get_local_ip_for_remote_ip {
837 #       my $remote_ip= shift;
838 #       my $result="0.0.0.0";
840 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
841 #               if($remote_ip eq "127.0.0.1") {
842 #                       $result = "127.0.0.1";
843 #               } else {
844 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
846 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
847 #                               or die "Could not open $PROC_NET_ROUTE";
849 #                       my @ifs = <PROC_NET_ROUTE>;
851 #                       close(PROC_NET_ROUTE);
853 #                       # Eat header line
854 #                       shift @ifs;
855 #                       chomp @ifs;
856 #                       foreach my $line(@ifs) {
857 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
858 #                               my $destination;
859 #                               my $mask;
860 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
861 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
862 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
863 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
864 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
865 #                                       # destination matches route, save mac and exit
866 #                                       $result= &get_ip($Iface);
867 #                                       last;
868 #                               }
869 #                       }
870 #               }
871 #       } else {
872 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
873 #       }
874 #       return $result;
875 #}
878 sub send_msg_to_target {
879     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
880     my $error = 0;
881     my $header;
882     my $timestamp = &get_time();
883     my $new_status;
884     my $act_status;
885     my ($sql_statement, $res);
886   
887     if( $msg_header ) {
888         $header = "'$msg_header'-";
889     } else {
890         $header = "";
891     }
893         # Patch the source ip
894         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
895                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
896                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
897         }
899     # encrypt xml msg
900     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
902     # opensocket
903     my $socket = &open_socket($address);
904     if( !$socket ) {
905         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
906         $error++;
907     }
908     
909     if( $error == 0 ) {
910         # send xml msg
911         print $socket $crypted_msg."\n";
913         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
914         daemon_log("$session_id DEBUG: message:\n$msg", 9);
915         
916     }
918     # close socket in any case
919     if( $socket ) {
920         close $socket;
921     }
923     if( $error > 0 ) { $new_status = "down"; }
924     else { $new_status = $msg_header; }
927     # known_clients
928     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
929     $res = $known_clients_db->select_dbentry($sql_statement);
930     if( keys(%$res) == 1) {
931         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
932         if ($act_status eq "down" && $new_status eq "down") {
933             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
934             $res = $known_clients_db->del_dbentry($sql_statement);
935             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
936         } else { 
937             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
938             $res = $known_clients_db->update_dbentry($sql_statement);
939             if($new_status eq "down"){
940                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
941             } else {
942                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
943             }
944         }
945     }
947     # known_server
948     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
949     $res = $known_server_db->select_dbentry($sql_statement);
950     if( keys(%$res) == 1) {
951         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
952         if ($act_status eq "down" && $new_status eq "down") {
953             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
954             $res = $known_server_db->del_dbentry($sql_statement);
955             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
956         } 
957         else { 
958             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
959             $res = $known_server_db->update_dbentry($sql_statement);
960             if($new_status eq "down"){
961                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
962             } else {
963                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
964             }
965         }
966     }
967     return $error; 
971 sub update_jobdb_status_for_send_msgs {
972     my ($answer, $error) = @_;
973     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
974         my $jobdb_id = $1;
975             
976         # sending msg faild
977         if( $error ) {
978             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
979                 my $sql_statement = "UPDATE $job_queue_tn ".
980                     "SET status='error', result='can not deliver msg, please consult log file' ".
981                     "WHERE id=$jobdb_id";
982                 my $res = $job_db->update_dbentry($sql_statement);
983             }
985         # sending msg was successful
986         } else {
987             my $sql_statement = "UPDATE $job_queue_tn ".
988                 "SET status='done' ".
989                 "WHERE id=$jobdb_id AND status='processed'";
990             my $res = $job_db->update_dbentry($sql_statement);
991         }
992     }
996 sub sig_handler {
997         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
998         daemon_log("0 INFO got signal '$signal'", 1); 
999         $kernel->sig_handled();
1000         return;
1004 sub msg_to_decrypt {
1005     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1006     my $session_id = $session->ID;
1007     my ($msg, $msg_hash, $module);
1008     my $error = 0;
1010     # hole neue msg aus @msgs_to_decrypt
1011     my $next_msg = shift @msgs_to_decrypt;
1012     
1013     # entschlüssle sie
1015     # msg is from a new client or gosa
1016     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1017     # msg is from a gosa-si-server
1018     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1019         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1020     }
1021     # msg is from a gosa-si-client
1022     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1023         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1024     }
1025     # an error occurred
1026     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1027         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1028         # could not understand a msg from its server the client cause a re-registering process
1029         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1030                 "' to cause a re-registering of the client if necessary", 3);
1031         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1032         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1033         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1034             my $host_name = $hit->{'hostname'};
1035             my $host_key = $hit->{'hostkey'};
1036             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1037             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1038             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1039         }
1040         $error++;
1041     }
1044     my $header;
1045     my $target;
1046     my $source;
1047     my $done = 0;
1048     my $sql;
1049     my $res;
1051     # check whether this message should be processed here
1052     if ($error == 0) {
1053         $header = @{$msg_hash->{'header'}}[0];
1054         $target = @{$msg_hash->{'target'}}[0];
1055         $source = @{$msg_hash->{'source'}}[0];
1056                 my $not_found_in_known_clients_db = 0;
1057                 my $not_found_in_known_server_db = 0;
1058                 my $not_found_in_foreign_clients_db = 0;
1059         my $local_address;
1060         my $local_mac;
1061         my ($target_ip, $target_port) = split(':', $target);
1062         
1063         # Determine the local ip address if target is an ip address
1064                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1065                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1066                 } else {
1067             $local_address = $server_address;
1068         }
1069         
1070         # Determine the local mac address if target is a mac address
1071         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) {
1072             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1073             my $network_interface= &get_interface_for_ip($loc_ip);
1074             $local_mac = &get_mac_for_interface($network_interface);
1075         } else {
1076             $local_mac = $server_mac_address;
1077         }
1079         # target and source is equal to GOSA -> process here
1080         if (not $done) {
1081             if ($target eq "GOSA" && $source eq "GOSA") {
1082                 $done = 1;                    
1083             }
1084         }
1086         # target is own address without forward_to_gosa-tag -> process here
1087         if (not $done) {
1088             if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1089                 $done = 1;
1090                 if ($source eq "GOSA") {
1091                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1092                 }
1093                 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1094             }
1095         }
1097         # target is a client address in known_clients -> process here
1098                 if (not $done) {
1099                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1100                                 $res = $known_clients_db->select_dbentry($sql);
1101                                 if (keys(%$res) > 0) {
1102                                                 $done = 1; 
1103                                                 my $hostname = $res->{1}->{'hostname'};
1104                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1105                         #print STDERR "target is a client address in known_clients -> process here\n";
1106                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1107                         if ($source eq "GOSA") {
1108                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1109                         }
1111                                 } else {
1112                                                 $not_found_in_known_clients_db = 1;
1113                                 }
1114                 }
1115         
1116         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1117         if (not $done) {
1118             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1119             my $gosa_at;
1120             my $gosa_session_id;
1121             if ((($target eq $local_address) || ($target eq $local_mac) ) && (defined $forward_to_gosa)){
1122                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1123                 if ($gosa_at ne $local_address) {
1124                     $done = 1;
1125                     #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n"; 
1126                 }
1127             }
1128         }
1130         # if message should be processed here -> add message to incoming_db
1131                 if ($done) {
1132                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1133                                 # so gosa-si-server knows how to process this kind of messages
1134                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1135                                                 $module = "GosaPackages";
1136                                 }
1138                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1139                                                                 primkey=>[],
1140                                                                 headertag=>$header,
1141                                                                 targettag=>$target,
1142                                                                 xmlmessage=>&encode_base64($msg),
1143                                                                 timestamp=>&get_time,
1144                                                                 module=>$module,
1145                                                                 sessionid=>$session_id,
1146                                                                 } );
1147                 }
1149         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1150         if (not $done) {
1151             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1152             my $gosa_at;
1153             my $gosa_session_id;
1154             if ((($target eq $local_address) || ($target eq $local_mac) ) && (defined $forward_to_gosa)){
1155                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1156                 if ($gosa_at eq $local_address) {
1157                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1158                     if( defined $session_reference ) {
1159                         $heap = $session_reference->get_heap();
1160                     }
1161                     if(exists $heap->{'client'}) {
1162                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1163                         $heap->{'client'}->put($msg);
1164                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1165                     }
1166                     $done = 1;
1167                     #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1168                 }
1169             }
1171         }
1173         # target is a client address in foreign_clients -> forward to registration server
1174         if (not $done) {
1175             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1176             $res = $foreign_clients_db->select_dbentry($sql);
1177             if (keys(%$res) > 0) {
1178                     my $hostname = $res->{1}->{'hostname'};
1179                     my ($host_ip, $host_port) = split(/:/, $hostname);
1180                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1181                 my $regserver = $res->{1}->{'regserver'};
1182                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1183                 my $res = $known_server_db->select_dbentry($sql);
1184                 if (keys(%$res) > 0) {
1185                     my $regserver_key = $res->{1}->{'hostkey'};
1186                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1187                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1188                     if ($source eq "GOSA") {
1189                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1190                     }
1191                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1192                 }
1193                 $done = 1;
1194                 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1195             } else {
1196                                 $not_found_in_foreign_clients_db = 1;
1197                         }
1198         }
1200         # target is a server address -> forward to server
1201         if (not $done) {
1202             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1203             $res = $known_server_db->select_dbentry($sql);
1204             if (keys(%$res) > 0) {
1205                 my $hostkey = $res->{1}->{'hostkey'};
1207                 if ($source eq "GOSA") {
1208                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1209                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1211                 }
1213                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1214                 $done = 1;
1215             } else {
1216                                 $not_found_in_known_server_db = 1;
1217                         }
1218         }
1220                 
1221                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1222                 if ( $not_found_in_foreign_clients_db 
1223                                                 && $not_found_in_known_server_db
1224                                                 && $not_found_in_known_clients_db) {
1225                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1226                                                                 primkey=>[],
1227                                                                 headertag=>$header,
1228                                                                 targettag=>$target,
1229                                                                 xmlmessage=>&encode_base64($msg),
1230                                                                 timestamp=>&get_time,
1231                                                                 module=>$module,
1232                                                                 sessionid=>$session_id,
1233                                                                 } );
1234                                 $done = 1;
1235                 }
1238         if (not $done) {
1239             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1240             if ($source eq "GOSA") {
1241                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1242                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1244                 my $session_reference = $kernel->ID_id_to_session($session_id);
1245                 if( defined $session_reference ) {
1246                     $heap = $session_reference->get_heap();
1247                 }
1248                 if(exists $heap->{'client'}) {
1249                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1250                     $heap->{'client'}->put($error_msg);
1251                 }
1252             }
1253         }
1255     }
1257     return;
1261 sub next_task {
1262     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1263     my $running_task = POE::Wheel::Run->new(
1264             Program => sub { process_task($session, $heap, $task) },
1265             StdioFilter => POE::Filter::Reference->new(),
1266             StdoutEvent  => "task_result",
1267             StderrEvent  => "task_debug",
1268             CloseEvent   => "task_done",
1269             );
1270     $heap->{task}->{ $running_task->ID } = $running_task;
1273 sub handle_task_result {
1274     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1275     my $client_answer = $result->{'answer'};
1276     if( $client_answer =~ s/session_id=(\d+)$// ) {
1277         my $session_id = $1;
1278         if( defined $session_id ) {
1279             my $session_reference = $kernel->ID_id_to_session($session_id);
1280             if( defined $session_reference ) {
1281                 $heap = $session_reference->get_heap();
1282             }
1283         }
1285         if(exists $heap->{'client'}) {
1286             $heap->{'client'}->put($client_answer);
1287         }
1288     }
1289     $kernel->sig(CHLD => "child_reap");
1292 sub handle_task_debug {
1293     my $result = $_[ARG0];
1294     print STDERR "$result\n";
1297 sub handle_task_done {
1298     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1299     delete $heap->{task}->{$task_id};
1302 sub process_task {
1303     no strict "refs";
1304     #CHECK: Not @_[...]?
1305     my ($session, $heap, $task) = @_;
1306     my $error = 0;
1307     my $answer_l;
1308     my ($answer_header, @answer_target_l, $answer_source);
1309     my $client_answer = "";
1311     # prepare all variables needed to process message
1312     #my $msg = $task->{'xmlmessage'};
1313     my $msg = &decode_base64($task->{'xmlmessage'});
1314     my $incoming_id = $task->{'id'};
1315     my $module = $task->{'module'};
1316     my $header =  $task->{'headertag'};
1317     my $session_id = $task->{'sessionid'};
1318     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1319     my $source = @{$msg_hash->{'source'}}[0];
1320     
1321     # set timestamp of incoming client uptodate, so client will not 
1322     # be deleted from known_clients because of expiration
1323     my $act_time = &get_time();
1324     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1325     my $res = $known_clients_db->exec_statement($sql);
1327     ######################
1328     # process incoming msg
1329     if( $error == 0) {
1330         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1331         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1332         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1334         if ( 0 < @{$answer_l} ) {
1335             my $answer_str = join("\n", @{$answer_l});
1336             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1337                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1338             }
1339             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1340         } else {
1341             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1342         }
1344     }
1345     if( !$answer_l ) { $error++ };
1347     ########
1348     # answer
1349     if( $error == 0 ) {
1351         foreach my $answer ( @{$answer_l} ) {
1352             # check outgoing msg to xml validity
1353             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1354             if( not defined $answer_hash ) { next; }
1355             
1356             $answer_header = @{$answer_hash->{'header'}}[0];
1357             @answer_target_l = @{$answer_hash->{'target'}};
1358             $answer_source = @{$answer_hash->{'source'}}[0];
1360             # deliver msg to all targets 
1361             foreach my $answer_target ( @answer_target_l ) {
1363                 # targets of msg are all gosa-si-clients in known_clients_db
1364                 if( $answer_target eq "*" ) {
1365                     # answer is for all clients
1366                     my $sql_statement= "SELECT * FROM known_clients";
1367                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1368                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1369                         my $host_name = $hit->{hostname};
1370                         my $host_key = $hit->{hostkey};
1371                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1372                         &update_jobdb_status_for_send_msgs($answer, $error);
1373                     }
1374                 }
1376                 # targets of msg are all gosa-si-server in known_server_db
1377                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1378                     # answer is for all server in known_server
1379                     my $sql_statement= "SELECT * FROM $known_server_tn";
1380                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1381                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1382                         my $host_name = $hit->{hostname};
1383                         my $host_key = $hit->{hostkey};
1384                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1385                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1386                         &update_jobdb_status_for_send_msgs($answer, $error);
1387                     }
1388                 }
1390                 # target of msg is GOsa
1391                                 elsif( $answer_target eq "GOSA" ) {
1392                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1393                                         my $add_on = "";
1394                     if( defined $session_id ) {
1395                         $add_on = ".session_id=$session_id";
1396                     }
1397                     # answer is for GOSA and has to returned to connected client
1398                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1399                     $client_answer = $gosa_answer.$add_on;
1400                 }
1402                 # target of msg is job queue at this host
1403                 elsif( $answer_target eq "JOBDB") {
1404                     $answer =~ /<header>(\S+)<\/header>/;   
1405                     my $header;
1406                     if( defined $1 ) { $header = $1; }
1407                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1408                     &update_jobdb_status_for_send_msgs($answer, $error);
1409                 }
1411                 # Target of msg is a mac address
1412                 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 ) {
1413                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1414                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1415                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1416                     my $found_ip_flag = 0;
1417                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1418                         my $host_name = $hit->{hostname};
1419                         my $host_key = $hit->{hostkey};
1420                         $answer =~ s/$answer_target/$host_name/g;
1421                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1422                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1423                         &update_jobdb_status_for_send_msgs($answer, $error);
1424                         $found_ip_flag++ ;
1425                     }   
1426                     if ($found_ip_flag == 0) {
1427                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1428                         my $res = $foreign_clients_db->select_dbentry($sql);
1429                         while( my ($hit_num, $hit) = each %{ $res } ) {
1430                             my $host_name = $hit->{hostname};
1431                             my $reg_server = $hit->{regserver};
1432                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1433                             
1434                             # Fetch key for reg_server
1435                             my $reg_server_key;
1436                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1437                             my $res = $known_server_db->select_dbentry($sql);
1438                             if (exists $res->{1}) {
1439                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1440                             } else {
1441                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1442                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1443                                 $reg_server_key = undef;
1444                             }
1446                             # Send answer to server where client is registered
1447                             if (defined $reg_server_key) {
1448                                 $answer =~ s/$answer_target/$host_name/g;
1449                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1450                                 &update_jobdb_status_for_send_msgs($answer, $error);
1451                                 $found_ip_flag++ ;
1452                             }
1453                         }
1454                     }
1455                     if( $found_ip_flag == 0) {
1456                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1457                     }
1459                 # Answer is for one specific host   
1460                 } else {
1461                     # get encrypt_key
1462                     my $encrypt_key = &get_encrypt_key($answer_target);
1463                     if( not defined $encrypt_key ) {
1464                         # unknown target
1465                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1466                         next;
1467                     }
1468                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1469                     &update_jobdb_status_for_send_msgs($answer, $error);
1470                 }
1471             }
1472         }
1473     }
1475     my $filter = POE::Filter::Reference->new();
1476     my %result = ( 
1477             status => "seems ok to me",
1478             answer => $client_answer,
1479             );
1481     my $output = $filter->put( [ \%result ] );
1482     print @$output;
1487 sub session_start {
1488     my ($kernel) = $_[KERNEL];
1489     $global_kernel = $kernel;
1490     $kernel->yield('register_at_foreign_servers');
1491         $kernel->yield('create_fai_server_db', $fai_server_tn );
1492         $kernel->yield('create_fai_release_db', $fai_release_tn );
1493     $kernel->yield('watch_for_next_tasks');
1494         $kernel->sig(USR1 => "sig_handler");
1495         $kernel->sig(USR2 => "recreate_packages_db");
1496         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1497         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1498     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1499         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1500     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1501         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1502     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1504     # Start opsi check
1505     if ($opsi_enabled eq "true") {
1506         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1507     }
1512 sub watch_for_done_jobs {
1513     #CHECK: $heap for what?
1514     my ($kernel,$heap) = @_[KERNEL, HEAP];
1516     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1517         my $res = $job_db->select_dbentry( $sql_statement );
1519     while( my ($id, $hit) = each %{$res} ) {
1520         my $jobdb_id = $hit->{id};
1521         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1522         my $res = $job_db->del_dbentry($sql_statement); 
1523     }
1525     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1529 sub watch_for_opsi_jobs {
1530     my ($kernel) = $_[KERNEL];
1532     # 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 
1533     # opsi install job is to parse the xml message. There is still the correct header.
1534     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1535         my $res = $job_db->select_dbentry( $sql_statement );
1537     # Ask OPSI for an update of the running jobs
1538     while (my ($id, $hit) = each %$res ) {
1539         # Determine current parameters of the job
1540         my $hostId = $hit->{'plainname'};
1541         my $macaddress = $hit->{'macaddress'};
1542         my $progress = $hit->{'progress'};
1544         my $result= {};
1545         
1546         # For hosts, only return the products that are or get installed
1547         my $callobj;
1548         $callobj = {
1549             method  => 'getProductStates_hash',
1550             params  => [ $hostId ],
1551             id  => 1,
1552         };
1553         
1554         my $hres = $opsi_client->call($opsi_url, $callobj);
1555         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1556         if (not &check_opsi_res($hres)) {
1557             my $htmp= $hres->result->{$hostId};
1558         
1559             # Check state != not_installed or action == setup -> load and add
1560             my $products= 0;
1561             my $installed= 0;
1562             my $installing = 0;
1563             my $error= 0;  
1564             my @installed_list;
1565             my @error_list;
1566             my $act_status = "none";
1567             foreach my $product (@{$htmp}){
1569                 if ($product->{'installationStatus'} ne "not_installed" or
1570                         $product->{'actionRequest'} eq "setup"){
1572                     # Increase number of products for this host
1573                     $products++;
1574         
1575                     if ($product->{'installationStatus'} eq "failed"){
1576                         $result->{$product->{'productId'}}= "error";
1577                         unshift(@error_list, $product->{'productId'});
1578                         $error++;
1579                     }
1580                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1581                         $result->{$product->{'productId'}}= "installed";
1582                         unshift(@installed_list, $product->{'productId'});
1583                         $installed++;
1584                     }
1585                     if ($product->{'installationStatus'} eq "installing"){
1586                         $result->{$product->{'productId'}}= "installing";
1587                         $installing++;
1588                         $act_status = "installing - ".$product->{'productId'};
1589                     }
1590                 }
1591             }
1592         
1593             # Estimate "rough" progress, avoid division by zero
1594             if ($products == 0) {
1595                 $result->{'progress'}= 0;
1596             } else {
1597                 $result->{'progress'}= int($installed * 100 / $products);
1598             }
1600             # Set updates in job queue
1601             if ((not $error) && (not $installing) && ($installed)) {
1602                 $act_status = "installed - ".join(", ", @installed_list);
1603             }
1604             if ($error) {
1605                 $act_status = "error - ".join(", ", @error_list);
1606             }
1607             if ($progress ne $result->{'progress'} ) {
1608                 # Updating progress and result 
1609                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1610                 my $update_res = $job_db->update_dbentry($update_statement);
1611             }
1612             if ($progress eq 100) { 
1613                 # Updateing status
1614                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1615                 if ($error) {
1616                     $done_statement .= "status='error'";
1617                 } else {
1618                     $done_statement .= "status='done'";
1619                 }
1620                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1621                 my $done_res = $job_db->update_dbentry($done_statement);
1622             }
1625         }
1626     }
1628     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1632 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1633 sub watch_for_modified_jobs {
1634     my ($kernel,$heap) = @_[KERNEL, HEAP];
1636     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1637     my $res = $job_db->select_dbentry( $sql_statement );
1638     
1639     # if db contains no jobs which should be update, do nothing
1640     if (keys %$res != 0) {
1642         if ($job_synchronization  eq "true") {
1643             # make out of the db result a gosa-si message   
1644             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1645  
1646             # update all other SI-server
1647             &inform_all_other_si_server($update_msg);
1648         }
1650         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1651         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1652         $res = $job_db->update_dbentry($sql_statement);
1653     }
1655     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1659 sub watch_for_new_jobs {
1660         if($watch_for_new_jobs_in_progress == 0) {
1661                 $watch_for_new_jobs_in_progress = 1;
1662                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1664                 # check gosa job quaeue for jobs with executable timestamp
1665                 my $timestamp = &get_time();
1666                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1667                 my $res = $job_db->exec_statement( $sql_statement );
1669                 # Merge all new jobs that would do the same actions
1670                 my @drops;
1671                 my $hits;
1672                 foreach my $hit (reverse @{$res} ) {
1673                         my $macaddress= lc @{$hit}[8];
1674                         my $headertag= @{$hit}[5];
1675                         if(
1676                                 defined($hits->{$macaddress}) &&
1677                                 defined($hits->{$macaddress}->{$headertag}) &&
1678                                 defined($hits->{$macaddress}->{$headertag}[0])
1679                         ) {
1680                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1681                         }
1682                         $hits->{$macaddress}->{$headertag}= $hit;
1683                 }
1685                 # Delete new jobs with a matching job in state 'processing'
1686                 foreach my $macaddress (keys %{$hits}) {
1687                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1688                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1689                                 if(defined($jobdb_id)) {
1690                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1691                                         my $res = $job_db->exec_statement( $sql_statement );
1692                                         foreach my $hit (@{$res}) {
1693                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1694                                         }
1695                                 } else {
1696                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1697                                 }
1698                         }
1699                 }
1701                 # Commit deletion
1702                 $job_db->exec_statementlist(\@drops);
1704                 # Look for new jobs that could be executed
1705                 foreach my $macaddress (keys %{$hits}) {
1707                         # Look if there is an executing job
1708                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1709                         my $res = $job_db->exec_statement( $sql_statement );
1711                         # Skip new jobs for host if there is a processing job
1712                         if(defined($res) and defined @{$res}[0]) {
1713                                 next;
1714                         }
1716                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1717                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1718                                 if(defined($jobdb_id)) {
1719                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1721                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1722                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1723                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1725                                         # expect macaddress is unique!!!!!!
1726                                         my $target = $res_hash->{1}->{hostname};
1728                                         # change header
1729                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1731                                         # add sqlite_id
1732                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1734                                         $job_msg =~ /<header>(\S+)<\/header>/;
1735                                         my $header = $1 ;
1736                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1738                                         # update status in job queue to 'processing'
1739                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1740                                         my $res = $job_db->update_dbentry($sql_statement);
1741 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1743                                         # We don't want parallel processing
1744                                         last;
1745                                 }
1746                         }
1747                 }
1749                 $watch_for_new_jobs_in_progress = 0;
1750                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1751         }
1755 sub watch_for_new_messages {
1756     my ($kernel,$heap) = @_[KERNEL, HEAP];
1757     my @coll_user_msg;   # collection list of outgoing messages
1758     
1759     # check messaging_db for new incoming messages with executable timestamp
1760     my $timestamp = &get_time();
1761     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1762     my $res = $messaging_db->exec_statement( $sql_statement );
1763         foreach my $hit (@{$res}) {
1765         # create outgoing messages
1766         my $message_to = @{$hit}[3];
1767         # translate message_to to plain login name
1768         my @message_to_l = split(/,/, $message_to);  
1769                 my %receiver_h; 
1770                 foreach my $receiver (@message_to_l) {
1771                         if ($receiver =~ /^u_([\s\S]*)$/) {
1772                                 $receiver_h{$1} = 0;
1773                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1774                                 my $group_name = $1;
1775                                 # fetch all group members from ldap and add them to receiver hash
1776                                 my $ldap_handle = &get_ldap_handle();
1777                                 if (defined $ldap_handle) {
1778                                                 my $mesg = $ldap_handle->search(
1779                                                                                 base => $ldap_base,
1780                                                                                 scope => 'sub',
1781                                                                                 attrs => ['memberUid'],
1782                                                                                 filter => "cn=$group_name",
1783                                                                                 );
1784                                                 if ($mesg->count) {
1785                                                                 my @entries = $mesg->entries;
1786                                                                 foreach my $entry (@entries) {
1787                                                                                 my @receivers= $entry->get_value("memberUid");
1788                                                                                 foreach my $receiver (@receivers) { 
1789                                                                                                 $receiver_h{$1} = 0;
1790                                                                                 }
1791                                                                 }
1792                                                 } 
1793                                                 # translating errors ?
1794                                                 if ($mesg->code) {
1795                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1796                                                 }
1797                                 # ldap handle error ?           
1798                                 } else {
1799                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1800                                 }
1801                         } else {
1802                                 my $sbjct = &encode_base64(@{$hit}[1]);
1803                                 my $msg = &encode_base64(@{$hit}[7]);
1804                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1805                         }
1806                 }
1807                 my @receiver_l = keys(%receiver_h);
1809         my $message_id = @{$hit}[0];
1811         #add each outgoing msg to messaging_db
1812         my $receiver;
1813         foreach $receiver (@receiver_l) {
1814             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1815                 "VALUES ('".
1816                 $message_id."', '".    # id
1817                 @{$hit}[1]."', '".     # subject
1818                 @{$hit}[2]."', '".     # message_from
1819                 $receiver."', '".      # message_to
1820                 "none"."', '".         # flag
1821                 "out"."', '".          # direction
1822                 @{$hit}[6]."', '".     # delivery_time
1823                 @{$hit}[7]."', '".     # message
1824                 $timestamp."'".     # timestamp
1825                 ")";
1826             &daemon_log("M DEBUG: $sql_statement", 1);
1827             my $res = $messaging_db->exec_statement($sql_statement);
1828             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1829         }
1831         # set incoming message to flag d=deliverd
1832         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1833         &daemon_log("M DEBUG: $sql_statement", 7);
1834         $res = $messaging_db->update_dbentry($sql_statement);
1835         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1836     }
1838     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1839     return;
1842 sub watch_for_delivery_messages {
1843     my ($kernel, $heap) = @_[KERNEL, HEAP];
1845     # select outgoing messages
1846     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1847     #&daemon_log("0 DEBUG: $sql", 7);
1848     my $res = $messaging_db->exec_statement( $sql_statement );
1849     
1850     # build out msg for each    usr
1851     foreach my $hit (@{$res}) {
1852         my $receiver = @{$hit}[3];
1853         my $msg_id = @{$hit}[0];
1854         my $subject = @{$hit}[1];
1855         my $message = @{$hit}[7];
1857         # resolve usr -> host where usr is logged in
1858         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1859         #&daemon_log("0 DEBUG: $sql", 7);
1860         my $res = $login_users_db->exec_statement($sql);
1862         # reciver is logged in nowhere
1863         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1865                 my $send_succeed = 0;
1866                 foreach my $hit (@$res) {
1867                                 my $receiver_host = @$hit[0];
1868                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1870                                 # fetch key to encrypt msg propperly for usr/host
1871                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1872                                 &daemon_log("0 DEBUG: $sql", 7);
1873                                 my $res = $known_clients_db->exec_statement($sql);
1875                                 # host is already down
1876                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1878                                 # host is on
1879                                 my $receiver_key = @{@{$res}[0]}[2];
1880                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1881                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1882                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1883                                 if ($error == 0 ) {
1884                                         $send_succeed++ ;
1885                                 }
1886                 }
1888                 if ($send_succeed) {
1889                                 # set outgoing msg at db to deliverd
1890                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1891                                 &daemon_log("0 DEBUG: $sql", 7);
1892                                 my $res = $messaging_db->exec_statement($sql); 
1893                 }
1894         }
1896     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1897     return;
1901 sub watch_for_done_messages {
1902     my ($kernel,$heap) = @_[KERNEL, HEAP];
1904     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1905     #&daemon_log("0 DEBUG: $sql", 7);
1906     my $res = $messaging_db->exec_statement($sql); 
1908     foreach my $hit (@{$res}) {
1909         my $msg_id = @{$hit}[0];
1911         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1912         #&daemon_log("0 DEBUG: $sql", 7); 
1913         my $res = $messaging_db->exec_statement($sql);
1915         # not all usr msgs have been seen till now
1916         if ( ref(@$res[0]) eq "ARRAY") { next; }
1917         
1918         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1919         #&daemon_log("0 DEBUG: $sql", 7);
1920         $res = $messaging_db->exec_statement($sql);
1921     
1922     }
1924     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1925     return;
1929 sub watch_for_old_known_clients {
1930     my ($kernel,$heap) = @_[KERNEL, HEAP];
1932     my $sql_statement = "SELECT * FROM $known_clients_tn";
1933     my $res = $known_clients_db->select_dbentry( $sql_statement );
1935     my $act_time = int(&get_time());
1937     while ( my ($hit_num, $hit) = each %$res) {
1938         my $expired_timestamp = int($hit->{'timestamp'});
1939         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1940         my $dt = DateTime->new( year   => $1,
1941                 month  => $2,
1942                 day    => $3,
1943                 hour   => $4,
1944                 minute => $5,
1945                 second => $6,
1946                 );
1948         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1949         $expired_timestamp = $dt->ymd('').$dt->hms('');
1950         if ($act_time > $expired_timestamp) {
1951             my $hostname = $hit->{'hostname'};
1952             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1953             my $del_res = $known_clients_db->exec_statement($del_sql);
1955             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1956         }
1958     }
1960     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1964 sub watch_for_next_tasks {
1965     my ($kernel,$heap) = @_[KERNEL, HEAP];
1967     my $sql = "SELECT * FROM $incoming_tn";
1968     my $res = $incoming_db->select_dbentry($sql);
1970     while ( my ($hit_num, $hit) = each %$res) {
1971         my $headertag = $hit->{'headertag'};
1972         if ($headertag =~ /^answer_(\d+)/) {
1973             # do not start processing, this message is for a still running POE::Wheel
1974             next;
1975         }
1976         my $message_id = $hit->{'id'};
1977         $kernel->yield('next_task', $hit);
1979         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1980         my $res = $incoming_db->exec_statement($sql);
1981     }
1983     $kernel->delay_set('watch_for_next_tasks', 0.1); 
1987 sub get_ldap_handle {
1988         my ($session_id) = @_;
1989         my $heap;
1990         my $ldap_handle;
1992         if (not defined $session_id ) { $session_id = 0 };
1993         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1995         if ($session_id == 0) {
1996                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1997                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1998                 $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!"); 
2000         } else {
2001                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2002                 if( defined $session_reference ) {
2003                         $heap = $session_reference->get_heap();
2004                 }
2006                 if (not defined $heap) {
2007                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2008                         return;
2009                 }
2011                 # TODO: This "if" is nonsense, because it doesn't prove that the
2012                 #       used handle is still valid - or if we've to reconnect...
2013                 #if (not exists $heap->{ldap_handle}) {
2014                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2015                         $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!"); 
2016                         $heap->{ldap_handle} = $ldap_handle;
2017                 #}
2018         }
2019         return $ldap_handle;
2023 sub change_fai_state {
2024     my ($st, $targets, $session_id) = @_;
2025     $session_id = 0 if not defined $session_id;
2026     # Set FAI state to localboot
2027     my %mapActions= (
2028         reboot    => '',
2029         update    => 'softupdate',
2030         localboot => 'localboot',
2031         reinstall => 'install',
2032         rescan    => '',
2033         wake      => '',
2034         memcheck  => 'memcheck',
2035         sysinfo   => 'sysinfo',
2036         install   => 'install',
2037     );
2039     # Return if this is unknown
2040     if (!exists $mapActions{ $st }){
2041         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2042       return;
2043     }
2045     my $state= $mapActions{ $st };
2047     my $ldap_handle = &get_ldap_handle($session_id);
2048     if( defined($ldap_handle) ) {
2050       # Build search filter for hosts
2051         my $search= "(&(objectClass=GOhard)";
2052         foreach (@{$targets}){
2053             $search.= "(macAddress=$_)";
2054         }
2055         $search.= ")";
2057       # If there's any host inside of the search string, procress them
2058         if (!($search =~ /macAddress/)){
2059             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2060             return;
2061         }
2063       # Perform search for Unit Tag
2064       my $mesg = $ldap_handle->search(
2065           base   => $ldap_base,
2066           scope  => 'sub',
2067           attrs  => ['dn', 'FAIstate', 'objectClass'],
2068           filter => "$search"
2069           );
2071           if ($mesg->count) {
2072                   my @entries = $mesg->entries;
2073                   if (0 == @entries) {
2074                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2075                   }
2077                   foreach my $entry (@entries) {
2078                           # Only modify entry if it is not set to '$state'
2079                           if ($entry->get_value("FAIstate") ne "$state"){
2080                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2081                                   my $result;
2082                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2083                                   if (exists $tmp{'FAIobject'}){
2084                                           if ($state eq ''){
2085                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2086                                                           delete => [ FAIstate => [] ] ]);
2087                                           } else {
2088                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2089                                                           replace => [ FAIstate => $state ] ]);
2090                                           }
2091                                   } elsif ($state ne ''){
2092                                           $result= $ldap_handle->modify($entry->dn, changes => [
2093                                                   add     => [ objectClass => 'FAIobject' ],
2094                                                   add     => [ FAIstate => $state ] ]);
2095                                   }
2097                                   # Errors?
2098                                   if ($result->code){
2099                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2100                                   }
2101                           } else {
2102                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2103                           }  
2104                   }
2105           } else {
2106                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2107           }
2109     # if no ldap handle defined
2110     } else {
2111         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2112     }
2114         return;
2118 sub change_goto_state {
2119     my ($st, $targets, $session_id) = @_;
2120     $session_id = 0  if not defined $session_id;
2122     # Switch on or off?
2123     my $state= $st eq 'active' ? 'active': 'locked';
2125     my $ldap_handle = &get_ldap_handle($session_id);
2126     if( defined($ldap_handle) ) {
2128       # Build search filter for hosts
2129       my $search= "(&(objectClass=GOhard)";
2130       foreach (@{$targets}){
2131         $search.= "(macAddress=$_)";
2132       }
2133       $search.= ")";
2135       # If there's any host inside of the search string, procress them
2136       if (!($search =~ /macAddress/)){
2137         return;
2138       }
2140       # Perform search for Unit Tag
2141       my $mesg = $ldap_handle->search(
2142           base   => $ldap_base,
2143           scope  => 'sub',
2144           attrs  => ['dn', 'gotoMode'],
2145           filter => "$search"
2146           );
2148       if ($mesg->count) {
2149         my @entries = $mesg->entries;
2150         foreach my $entry (@entries) {
2152           # Only modify entry if it is not set to '$state'
2153           if ($entry->get_value("gotoMode") ne $state){
2155             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2156             my $result;
2157             $result= $ldap_handle->modify($entry->dn, changes => [
2158                                                 replace => [ gotoMode => $state ] ]);
2160             # Errors?
2161             if ($result->code){
2162               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2163             }
2165           }
2166         }
2167       } else {
2168                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2169           }
2171     }
2175 sub run_recreate_packages_db {
2176     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2177     my $session_id = $session->ID;
2178         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2179         $kernel->yield('create_fai_release_db', $fai_release_tn);
2180         $kernel->yield('create_fai_server_db', $fai_server_tn);
2181         return;
2185 sub run_create_fai_server_db {
2186     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2187     my $session_id = $session->ID;
2188     my $task = POE::Wheel::Run->new(
2189             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2190             StdoutEvent  => "session_run_result",
2191             StderrEvent  => "session_run_debug",
2192             CloseEvent   => "session_run_done",
2193             );
2195     $heap->{task}->{ $task->ID } = $task;
2196     return;
2200 sub create_fai_server_db {
2201     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2202         my $result;
2204         if (not defined $session_id) { $session_id = 0; }
2205     my $ldap_handle = &get_ldap_handle();
2206         if(defined($ldap_handle)) {
2207                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2208                 my $mesg= $ldap_handle->search(
2209                         base   => $ldap_base,
2210                         scope  => 'sub',
2211                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2212                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2213                 );
2214                 if($mesg->{'resultCode'} == 0 &&
2215                    $mesg->count != 0) {
2216                    foreach my $entry (@{$mesg->{entries}}) {
2217                            if($entry->exists('FAIrepository')) {
2218                                    # Add an entry for each Repository configured for server
2219                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2220                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2221                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2222                                                    $result= $fai_server_db->add_dbentry( { 
2223                                                                    table => $table_name,
2224                                                                    primkey => ['server', 'release', 'tag'],
2225                                                                    server => $tmp_url,
2226                                                                    release => $tmp_release,
2227                                                                    sections => $tmp_sections,
2228                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2229                                                            } );
2230                                            }
2231                                    }
2232                            }
2233                    }
2234                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2236                 # TODO: Find a way to post the 'create_packages_list_db' event
2237                 if(not defined($dont_create_packages_list)) {
2238                         &create_packages_list_db(undef, undef, $session_id);
2239                 }
2240         }       
2241     
2242     $ldap_handle->disconnect;
2243         return $result;
2247 sub run_create_fai_release_db {
2248     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2249         my $session_id = $session->ID;
2250     my $task = POE::Wheel::Run->new(
2251             Program => sub { &create_fai_release_db($table_name, $session_id) },
2252             StdoutEvent  => "session_run_result",
2253             StderrEvent  => "session_run_debug",
2254             CloseEvent   => "session_run_done",
2255             );
2257     $heap->{task}->{ $task->ID } = $task;
2258     return;
2262 sub create_fai_release_db {
2263         my ($table_name, $session_id) = @_;
2264         my $result;
2266     # used for logging
2267     if (not defined $session_id) { $session_id = 0; }
2269     my $ldap_handle = &get_ldap_handle();
2270         if(defined($ldap_handle)) {
2271                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2272                 my $mesg= $ldap_handle->search(
2273                         base   => $ldap_base,
2274                         scope  => 'sub',
2275                         attrs  => [],
2276                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2277                 );
2278                 if($mesg->{'resultCode'} == 0 &&
2279                         $mesg->count != 0) {
2280                         # Walk through all possible FAI container ou's
2281                         my @sql_list;
2282                         my $timestamp= &get_time();
2283                         foreach my $ou (@{$mesg->{entries}}) {
2284                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2285                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2286                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2287                                         if(@tmp_array) {
2288                                                 foreach my $entry (@tmp_array) {
2289                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2290                                                                 my $sql= 
2291                                                                 "INSERT INTO $table_name "
2292                                                                 ."(timestamp, release, class, type, state) VALUES ("
2293                                                                 .$timestamp.","
2294                                                                 ."'".$entry->{'release'}."',"
2295                                                                 ."'".$entry->{'class'}."',"
2296                                                                 ."'".$entry->{'type'}."',"
2297                                                                 ."'".$entry->{'state'}."')";
2298                                                                 push @sql_list, $sql;
2299                                                         }
2300                                                 }
2301                                         }
2302                                 }
2303                         }
2305                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2306                         if(@sql_list) {
2307                                 unshift @sql_list, "VACUUM";
2308                                 unshift @sql_list, "DELETE FROM $table_name";
2309                                 $fai_release_db->exec_statementlist(\@sql_list);
2310                         }
2311                         daemon_log("$session_id DEBUG: Done with inserting",7);
2312                 }
2313                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2314         }
2315     $ldap_handle->disconnect;
2316         return $result;
2319 sub get_fai_types {
2320         my $tmp_classes = shift || return undef;
2321         my @result;
2323         foreach my $type(keys %{$tmp_classes}) {
2324                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2325                         my $entry = {
2326                                 type => $type,
2327                                 state => $tmp_classes->{$type}[0],
2328                         };
2329                         push @result, $entry;
2330                 }
2331         }
2333         return @result;
2336 sub get_fai_state {
2337         my $result = "";
2338         my $tmp_classes = shift || return $result;
2340         foreach my $type(keys %{$tmp_classes}) {
2341                 if(defined($tmp_classes->{$type}[0])) {
2342                         $result = $tmp_classes->{$type}[0];
2343                         
2344                 # State is equal for all types in class
2345                         last;
2346                 }
2347         }
2349         return $result;
2352 sub resolve_fai_classes {
2353         my ($fai_base, $ldap_handle, $session_id) = @_;
2354         if (not defined $session_id) { $session_id = 0; }
2355         my $result;
2356         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2357         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2358         my $fai_classes;
2360         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2361         my $mesg= $ldap_handle->search(
2362                 base   => $fai_base,
2363                 scope  => 'sub',
2364                 attrs  => ['cn','objectClass','FAIstate'],
2365                 filter => $fai_filter,
2366         );
2367         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2369         if($mesg->{'resultCode'} == 0 &&
2370                 $mesg->count != 0) {
2371                 foreach my $entry (@{$mesg->{entries}}) {
2372                         if($entry->exists('cn')) {
2373                                 my $tmp_dn= $entry->dn();
2375                                 # Skip classname and ou dn parts for class
2376                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2378                                 # Skip classes without releases
2379                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2380                                         next;
2381                                 }
2383                                 my $tmp_cn= $entry->get_value('cn');
2384                                 my $tmp_state= $entry->get_value('FAIstate');
2386                                 my $tmp_type;
2387                                 # Get FAI type
2388                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2389                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2390                                                 $tmp_type= $oclass;
2391                                                 last;
2392                                         }
2393                                 }
2395                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2396                                         # A Subrelease
2397                                         my @sub_releases = split(/,/, $tmp_release);
2399                                         # Walk through subreleases and build hash tree
2400                                         my $hash;
2401                                         while(my $tmp_sub_release = pop @sub_releases) {
2402                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2403                                         }
2404                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2405                                 } else {
2406                                         # A branch, no subrelease
2407                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2408                                 }
2409                         } elsif (!$entry->exists('cn')) {
2410                                 my $tmp_dn= $entry->dn();
2411                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2413                                 # Skip classes without releases
2414                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2415                                         next;
2416                                 }
2418                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2419                                         # A Subrelease
2420                                         my @sub_releases= split(/,/, $tmp_release);
2422                                         # Walk through subreleases and build hash tree
2423                                         my $hash;
2424                                         while(my $tmp_sub_release = pop @sub_releases) {
2425                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2426                                         }
2427                                         # Remove the last two characters
2428                                         chop($hash);
2429                                         chop($hash);
2431                                         eval('$fai_classes->'.$hash.'= {}');
2432                                 } else {
2433                                         # A branch, no subrelease
2434                                         if(!exists($fai_classes->{$tmp_release})) {
2435                                                 $fai_classes->{$tmp_release} = {};
2436                                         }
2437                                 }
2438                         }
2439                 }
2441                 # The hash is complete, now we can honor the copy-on-write based missing entries
2442                 foreach my $release (keys %$fai_classes) {
2443                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2444                 }
2445         }
2446         return $result;
2449 sub apply_fai_inheritance {
2450        my $fai_classes = shift || return {};
2451        my $tmp_classes;
2453        # Get the classes from the branch
2454        foreach my $class (keys %{$fai_classes}) {
2455                # Skip subreleases
2456                if($class =~ /^ou=.*$/) {
2457                        next;
2458                } else {
2459                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2460                }
2461        }
2463        # Apply to each subrelease
2464        foreach my $subrelease (keys %{$fai_classes}) {
2465                if($subrelease =~ /ou=/) {
2466                        foreach my $tmp_class (keys %{$tmp_classes}) {
2467                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2468                                        $fai_classes->{$subrelease}->{$tmp_class} =
2469                                        deep_copy($tmp_classes->{$tmp_class});
2470                                } else {
2471                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2472                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2473                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2474                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2475                                                }
2476                                        }
2477                                }
2478                        }
2479                }
2480        }
2482        # Find subreleases in deeper levels
2483        foreach my $subrelease (keys %{$fai_classes}) {
2484                if($subrelease =~ /ou=/) {
2485                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2486                                if($subsubrelease =~ /ou=/) {
2487                                        apply_fai_inheritance($fai_classes->{$subrelease});
2488                                }
2489                        }
2490                }
2491        }
2493        return $fai_classes;
2496 sub get_fai_release_entries {
2497         my $tmp_classes = shift || return;
2498         my $parent = shift || "";
2499         my @result = shift || ();
2501         foreach my $entry (keys %{$tmp_classes}) {
2502                 if(defined($entry)) {
2503                         if($entry =~ /^ou=.*$/) {
2504                                 my $release_name = $entry;
2505                                 $release_name =~ s/ou=//g;
2506                                 if(length($parent)>0) {
2507                                         $release_name = $parent."/".$release_name;
2508                                 }
2509                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2510                                 foreach my $bufentry(@bufentries) {
2511                                         push @result, $bufentry;
2512                                 }
2513                         } else {
2514                                 my @types = get_fai_types($tmp_classes->{$entry});
2515                                 foreach my $type (@types) {
2516                                         push @result, 
2517                                         {
2518                                                 'class' => $entry,
2519                                                 'type' => $type->{'type'},
2520                                                 'release' => $parent,
2521                                                 'state' => $type->{'state'},
2522                                         };
2523                                 }
2524                         }
2525                 }
2526         }
2528         return @result;
2531 sub deep_copy {
2532         my $this = shift;
2533         if (not ref $this) {
2534                 $this;
2535         } elsif (ref $this eq "ARRAY") {
2536                 [map deep_copy($_), @$this];
2537         } elsif (ref $this eq "HASH") {
2538                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2539         } else { die "what type is $_?" }
2543 sub session_run_result {
2544     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2545     $kernel->sig(CHLD => "child_reap");
2548 sub session_run_debug {
2549     my $result = $_[ARG0];
2550     print STDERR "$result\n";
2553 sub session_run_done {
2554     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2555     delete $heap->{task}->{$task_id};
2559 sub create_sources_list {
2560         my $session_id = shift;
2561         my $ldap_handle = &main::get_ldap_handle;
2562         my $result="/tmp/gosa_si_tmp_sources_list";
2564         # Remove old file
2565         if(stat($result)) {
2566                 unlink($result);
2567                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2568         }
2570         my $fh;
2571         open($fh, ">$result");
2572         if (not defined $fh) {
2573                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2574                 return undef;
2575         }
2576         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2577                 my $mesg=$ldap_handle->search(
2578                         base    => $main::ldap_server_dn,
2579                         scope   => 'base',
2580                         attrs   => 'FAIrepository',
2581                         filter  => 'objectClass=FAIrepositoryServer'
2582                 );
2583                 if($mesg->count) {
2584                         foreach my $entry(@{$mesg->{'entries'}}) {
2585                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2586                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2587                                         my $line = "deb $server $release";
2588                                         $sections =~ s/,/ /g;
2589                                         $line.= " $sections";
2590                                         print $fh $line."\n";
2591                                 }
2592                         }
2593                 }
2594         } else {
2595                 if (defined $main::ldap_server_dn){
2596                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2597                 } else {
2598                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2599                 }
2600         }
2601         close($fh);
2603         return $result;
2607 sub run_create_packages_list_db {
2608     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2609         my $session_id = $session->ID;
2611         my $task = POE::Wheel::Run->new(
2612                                         Priority => +20,
2613                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2614                                         StdoutEvent  => "session_run_result",
2615                                         StderrEvent  => "session_run_debug",
2616                                         CloseEvent   => "session_run_done",
2617                                         );
2618         $heap->{task}->{ $task->ID } = $task;
2622 sub create_packages_list_db {
2623         my ($ldap_handle, $sources_file, $session_id) = @_;
2624         
2625         # it should not be possible to trigger a recreation of packages_list_db
2626         # while packages_list_db is under construction, so set flag packages_list_under_construction
2627         # which is tested befor recreation can be started
2628         if (-r $packages_list_under_construction) {
2629                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2630                 return;
2631         } else {
2632                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2633                 # set packages_list_under_construction to true
2634                 system("touch $packages_list_under_construction");
2635                 @packages_list_statements=();
2636         }
2638         if (not defined $session_id) { $session_id = 0; }
2639         if (not defined $ldap_handle) { 
2640                 $ldap_handle= &get_ldap_handle();
2642                 if (not defined $ldap_handle) {
2643                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2644                         unlink($packages_list_under_construction);
2645                         return;
2646                 }
2647         }
2648         if (not defined $sources_file) { 
2649                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2650                 $sources_file = &create_sources_list($session_id);
2651         }
2653         if (not defined $sources_file) {
2654                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2655                 unlink($packages_list_under_construction);
2656                 return;
2657         }
2659         my $line;
2661         open(CONFIG, "<$sources_file") or do {
2662                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2663                 unlink($packages_list_under_construction);
2664                 return;
2665         };
2667         # Read lines
2668         while ($line = <CONFIG>){
2669                 # Unify
2670                 chop($line);
2671                 $line =~ s/^\s+//;
2672                 $line =~ s/^\s+/ /;
2674                 # Strip comments
2675                 $line =~ s/#.*$//g;
2677                 # Skip empty lines
2678                 if ($line =~ /^\s*$/){
2679                         next;
2680                 }
2682                 # Interpret deb line
2683                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2684                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2685                         my $section;
2686                         foreach $section (split(' ', $sections)){
2687                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2688                         }
2689                 }
2690         }
2692         close (CONFIG);
2695         find(\&cleanup_and_extract, keys( %repo_dirs ));
2696         &main::strip_packages_list_statements();
2697         unshift @packages_list_statements, "VACUUM";
2698         $packages_list_db->exec_statementlist(\@packages_list_statements);
2699         unlink($packages_list_under_construction);
2700         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2701         return;
2704 # This function should do some intensive task to minimize the db-traffic
2705 sub strip_packages_list_statements {
2706     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2707         my @new_statement_list=();
2708         my $hash;
2709         my $insert_hash;
2710         my $update_hash;
2711         my $delete_hash;
2712         my $local_timestamp=get_time();
2714         foreach my $existing_entry (@existing_entries) {
2715                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2716         }
2718         foreach my $statement (@packages_list_statements) {
2719                 if($statement =~ /^INSERT/i) {
2720                         # Assign the values from the insert statement
2721                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2722                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2723                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2724                                 # If section or description has changed, update the DB
2725                                 if( 
2726                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2727                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2728                                 ) {
2729                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2730                                 }
2731                         } else {
2732                                 # Insert a non-existing entry to db
2733                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2734                         }
2735                 } elsif ($statement =~ /^UPDATE/i) {
2736                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2737                         /^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;
2738                         foreach my $distribution (keys %{$hash}) {
2739                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2740                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2741                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2742                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2743                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2744                                                 my $section;
2745                                                 my $description;
2746                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2747                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2748                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2749                                                 }
2750                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2751                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2752                                                 }
2753                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2754                                         }
2755                                 }
2756                         }
2757                 }
2758         }
2760         # TODO: Check for orphaned entries
2762         # unroll the insert_hash
2763         foreach my $distribution (keys %{$insert_hash}) {
2764                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2765                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2766                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2767                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2768                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2769                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2770                                 ."'$local_timestamp')";
2771                         }
2772                 }
2773         }
2775         # unroll the update hash
2776         foreach my $distribution (keys %{$update_hash}) {
2777                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2778                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2779                                 my $set = "";
2780                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2781                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2782                                 }
2783                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2784                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2785                                 }
2786                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2787                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2788                                 }
2789                                 if(defined($set) and length($set) > 0) {
2790                                         $set .= "timestamp = '$local_timestamp'";
2791                                 } else {
2792                                         next;
2793                                 }
2794                                 push @new_statement_list, 
2795                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2796                                         ." distribution = '$distribution'"
2797                                         ." AND package = '$package'"
2798                                         ." AND version = '$version'";
2799                         }
2800                 }
2801         }
2803         @packages_list_statements = @new_statement_list;
2807 sub parse_package_info {
2808     my ($baseurl, $dist, $section, $session_id)= @_;
2809     my ($package);
2810     if (not defined $session_id) { $session_id = 0; }
2811     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2812     $repo_dirs{ "${repo_path}/pool" } = 1;
2814     foreach $package ("Packages.gz"){
2815         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2816         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2817         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2818     }
2819     
2823 sub get_package {
2824     my ($url, $dest, $session_id)= @_;
2825     if (not defined $session_id) { $session_id = 0; }
2827     my $tpath = dirname($dest);
2828     -d "$tpath" || mkpath "$tpath";
2830     # This is ugly, but I've no time to take a look at "how it works in perl"
2831     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2832         system("gunzip -cd '$dest' > '$dest.in'");
2833         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2834         unlink($dest);
2835         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2836     } else {
2837         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2838     }
2839     return 0;
2843 sub parse_package {
2844     my ($path, $dist, $srv_path, $session_id)= @_;
2845     if (not defined $session_id) { $session_id = 0;}
2846     my ($package, $version, $section, $description);
2847     my $PACKAGES;
2848     my $timestamp = &get_time();
2850     if(not stat("$path.in")) {
2851         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2852         return;
2853     }
2855     open($PACKAGES, "<$path.in");
2856     if(not defined($PACKAGES)) {
2857         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2858         return;
2859     }
2861     # Read lines
2862     while (<$PACKAGES>){
2863         my $line = $_;
2864         # Unify
2865         chop($line);
2867         # Use empty lines as a trigger
2868         if ($line =~ /^\s*$/){
2869             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2870             push(@packages_list_statements, $sql);
2871             $package = "none";
2872             $version = "none";
2873             $section = "none";
2874             $description = "none"; 
2875             next;
2876         }
2878         # Trigger for package name
2879         if ($line =~ /^Package:\s/){
2880             ($package)= ($line =~ /^Package: (.*)$/);
2881             next;
2882         }
2884         # Trigger for version
2885         if ($line =~ /^Version:\s/){
2886             ($version)= ($line =~ /^Version: (.*)$/);
2887             next;
2888         }
2890         # Trigger for description
2891         if ($line =~ /^Description:\s/){
2892             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2893             next;
2894         }
2896         # Trigger for section
2897         if ($line =~ /^Section:\s/){
2898             ($section)= ($line =~ /^Section: (.*)$/);
2899             next;
2900         }
2902         # Trigger for filename
2903         if ($line =~ /^Filename:\s/){
2904             my ($filename) = ($line =~ /^Filename: (.*)$/);
2905             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2906             next;
2907         }
2908     }
2910     close( $PACKAGES );
2911     unlink( "$path.in" );
2915 sub store_fileinfo {
2916     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2918     my %fileinfo = (
2919         'package' => $package,
2920         'dist' => $dist,
2921         'version' => $vers,
2922     );
2924     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2928 sub cleanup_and_extract {
2929     my $fileinfo = $repo_files{ $File::Find::name };
2931     if( defined $fileinfo ) {
2933         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2934         my $sql;
2935         my $package = $fileinfo->{ 'package' };
2936         my $newver = $fileinfo->{ 'version' };
2938         mkpath($dir);
2939         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2941                 if( -f "$dir/DEBIAN/templates" ) {
2943                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
2945                         my $tmpl= "";
2946                         {
2947                                 local $/=undef;
2948                                 open FILE, "$dir/DEBIAN/templates";
2949                                 $tmpl = &encode_base64(<FILE>);
2950                                 close FILE;
2951                         }
2952                         rmtree("$dir/DEBIAN/templates");
2954                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2955                 push @packages_list_statements, $sql;
2956                 }
2957     }
2959     return;
2963 sub register_at_foreign_servers {   
2964     my ($kernel) = $_[KERNEL];
2966     # hole alle bekannten server aus known_server_db
2967     my $server_sql = "SELECT * FROM $known_server_tn";
2968     my $server_res = $known_server_db->exec_statement($server_sql);
2970     # no entries in known_server_db
2971     if (not ref(@$server_res[0]) eq "ARRAY") { 
2972         # TODO
2973     }
2975     # detect already connected clients
2976     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2977     my $client_res = $known_clients_db->exec_statement($client_sql);
2979     # send my server details to all other gosa-si-server within the network
2980     foreach my $hit (@$server_res) {
2981         my $hostname = @$hit[0];
2982         my $hostkey = &create_passwd;
2984         # add already connected clients to registration message 
2985         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2986         &add_content2xml_hash($myhash, 'key', $hostkey);
2987         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2989         # add locally loaded gosa-si modules to registration message
2990         my $loaded_modules = {};
2991         while (my ($package, $pck_info) = each %$known_modules) {
2992             foreach my $act_module (keys(%{@$pck_info[2]})) {
2993                 $loaded_modules->{$act_module} = ""; 
2994             }
2995         }
2996         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
2998         # add macaddress to registration message
2999         my ($host_ip, $host_port) = split(/:/, $hostname);
3000         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3001         my $network_interface= &get_interface_for_ip($local_ip);
3002         my $host_mac = &get_mac_for_interface($network_interface);
3003         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3004         
3005         # build registration message and send it
3006         my $foreign_server_msg = &create_xml_string($myhash);
3007         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3008     }
3009     
3010     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3011     return;
3015 #==== MAIN = main ==============================================================
3016 #  parse commandline options
3017 Getopt::Long::Configure( "bundling" );
3018 GetOptions("h|help" => \&usage,
3019         "c|config=s" => \$cfg_file,
3020         "f|foreground" => \$foreground,
3021         "v|verbose+" => \$verbose,
3022         "no-arp+" => \$no_arp,
3023            );
3025 #  read and set config parameters
3026 &check_cmdline_param ;
3027 &read_configfile($cfg_file, %cfg_defaults);
3028 &check_pid;
3030 $SIG{CHLD} = 'IGNORE';
3032 # forward error messages to logfile
3033 if( ! $foreground ) {
3034   open( STDIN,  '+>/dev/null' );
3035   open( STDOUT, '+>&STDIN'    );
3036   open( STDERR, '+>&STDIN'    );
3039 # Just fork, if we are not in foreground mode
3040 if( ! $foreground ) { 
3041     chdir '/'                 or die "Can't chdir to /: $!";
3042     $pid = fork;
3043     setsid                    or die "Can't start a new session: $!";
3044     umask 0;
3045 } else { 
3046     $pid = $$; 
3049 # Do something useful - put our PID into the pid_file
3050 if( 0 != $pid ) {
3051     open( LOCK_FILE, ">$pid_file" );
3052     print LOCK_FILE "$pid\n";
3053     close( LOCK_FILE );
3054     if( !$foreground ) { 
3055         exit( 0 ) 
3056     };
3059 # parse head url and revision from svn
3060 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3061 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3062 $server_headURL = defined $1 ? $1 : 'unknown' ;
3063 $server_revision = defined $2 ? $2 : 'unknown' ;
3064 if ($server_headURL =~ /\/tag\// || 
3065         $server_headURL =~ /\/branches\// ) {
3066     $server_status = "stable"; 
3067 } else {
3068     $server_status = "developmental" ;
3072 daemon_log(" ", 1);
3073 daemon_log("$0 started!", 1);
3074 daemon_log("status: $server_status", 1);
3075 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3077 # connect to incoming_db
3078 unlink($incoming_file_name);
3079 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3080 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3082 # connect to gosa-si job queue
3083 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3084 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3086 # connect to known_clients_db
3087 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3088 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3090 # connect to foreign_clients_db
3091 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3092 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3094 # connect to known_server_db
3095 unlink($known_server_file_name);
3096 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3097 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3099 # connect to login_usr_db
3100 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3101 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3103 # connect to fai_server_db and fai_release_db
3104 unlink($fai_server_file_name);
3105 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3106 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3108 unlink($fai_release_file_name);
3109 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3110 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3112 # connect to packages_list_db
3113 #unlink($packages_list_file_name);
3114 unlink($packages_list_under_construction);
3115 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3116 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3118 # connect to messaging_db
3119 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3120 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3123 # create xml object used for en/decrypting
3124 $xml = new XML::Simple();
3127 # foreign servers 
3128 my @foreign_server_list;
3130 # add foreign server from cfg file
3131 if ($foreign_server_string ne "") {
3132     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3133     foreach my $foreign_server (@cfg_foreign_server_list) {
3134         push(@foreign_server_list, $foreign_server);
3135     }
3138 # add foreign server from dns
3139 my @tmp_servers;
3140 if ( !$server_domain) {
3141     # Try our DNS Searchlist
3142     for my $domain(get_dns_domains()) {
3143         chomp($domain);
3144         my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3145         if(@$tmp_domains) {
3146             for my $tmp_server(@$tmp_domains) {
3147                 push @tmp_servers, $tmp_server;
3148             }
3149         }
3150     }
3151     if(@tmp_servers && length(@tmp_servers)==0) {
3152         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3153     }
3154 } else {
3155     @tmp_servers = &get_server_addresses($server_domain);
3156     if( 0 == @tmp_servers ) {
3157         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3158     }
3160 foreach my $server (@tmp_servers) { 
3161     unshift(@foreign_server_list, $server); 
3163 # eliminate duplicate entries
3164 @foreign_server_list = &del_doubles(@foreign_server_list);
3165 my $all_foreign_server = join(", ", @foreign_server_list);
3166 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3168 # add all found foreign servers to known_server
3169 my $act_timestamp = &get_time();
3170 foreach my $foreign_server (@foreign_server_list) {
3172         # do not add myself to known_server_db
3173         if (&is_local($foreign_server)) { next; }
3174         ######################################
3176     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3177             primkey=>['hostname'],
3178             hostname=>$foreign_server,
3179             macaddress=>"",
3180             status=>'not_jet_registered',
3181             hostkey=>"none",
3182             loaded_modules => "none", 
3183             timestamp=>$act_timestamp,
3184             } );
3188 # Import all modules
3189 &import_modules;
3191 # Check wether all modules are gosa-si valid passwd check
3192 &password_check;
3194 # Prepare for using Opsi 
3195 if ($opsi_enabled eq "true") {
3196     use JSON::RPC::Client;
3197     use XML::Quote qw(:all);
3198     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3199     $opsi_client = new JSON::RPC::Client;
3203 POE::Component::Server::TCP->new(
3204     Alias => "TCP_SERVER",
3205         Port => $server_port,
3206         ClientInput => sub {
3207         my ($kernel, $input) = @_[KERNEL, ARG0];
3208         push(@tasks, $input);
3209         push(@msgs_to_decrypt, $input);
3210         $kernel->yield("msg_to_decrypt");
3211         },
3212     InlineStates => {
3213         msg_to_decrypt => \&msg_to_decrypt,
3214         next_task => \&next_task,
3215         task_result => \&handle_task_result,
3216         task_done   => \&handle_task_done,
3217         task_debug  => \&handle_task_debug,
3218         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3219     }
3220 );
3222 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3224 # create session for repeatedly checking the job queue for jobs
3225 POE::Session->create(
3226         inline_states => {
3227                 _start => \&session_start,
3228         register_at_foreign_servers => \&register_at_foreign_servers,
3229         sig_handler => \&sig_handler,
3230         next_task => \&next_task,
3231         task_result => \&handle_task_result,
3232         task_done   => \&handle_task_done,
3233         task_debug  => \&handle_task_debug,
3234         watch_for_next_tasks => \&watch_for_next_tasks,
3235         watch_for_new_messages => \&watch_for_new_messages,
3236         watch_for_delivery_messages => \&watch_for_delivery_messages,
3237         watch_for_done_messages => \&watch_for_done_messages,
3238                 watch_for_new_jobs => \&watch_for_new_jobs,
3239         watch_for_modified_jobs => \&watch_for_modified_jobs,
3240         watch_for_done_jobs => \&watch_for_done_jobs,
3241         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3242         watch_for_old_known_clients => \&watch_for_old_known_clients,
3243         create_packages_list_db => \&run_create_packages_list_db,
3244         create_fai_server_db => \&run_create_fai_server_db,
3245         create_fai_release_db => \&run_create_fai_release_db,
3246                 recreate_packages_db => \&run_recreate_packages_db,
3247         session_run_result => \&session_run_result,
3248         session_run_debug => \&session_run_debug,
3249         session_run_done => \&session_run_done,
3250         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3251         }
3252 );
3255 POE::Kernel->run();
3256 exit;