Code

update: new handling of gotoLastSystem informations
[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::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file, 
91     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92     $arp_activ, $gosa_unit_tag,
93     $GosaPackages_key, $gosa_timeout,
94     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96     $arp_enabled, $arp_interface,
97     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98                 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135         "timestamp VARCHAR(14) DEFAULT 'none'", 
136         "headertag VARCHAR(255) DEFAULT 'none'",
137         "targettag VARCHAR(255) DEFAULT 'none'",
138         "xmlmessage TEXT",
139         "module VARCHAR(255) DEFAULT 'none'",
140         "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148         "timestamp VARCHAR(14) DEFAULT 'none'", 
149         "status VARCHAR(255) DEFAULT 'none'", 
150         "result TEXT",
151         "progress VARCHAR(255) DEFAULT 'none'",
152         "headertag VARCHAR(255) DEFAULT 'none'",
153         "targettag VARCHAR(255) DEFAULT 'none'", 
154         "xmlmessage TEXT", 
155         "macaddress VARCHAR(17) DEFAULT 'none'",
156         "plainname VARCHAR(255) DEFAULT 'none'",
157         "siserver VARCHAR(255) DEFAULT 'none'",
158         "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients"; 
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client 
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server"; 
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release"; 
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386"; 
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging"; 
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
208         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai"; 
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
224  
225 # Lifetime of logged in user information. If no update information comes after n seconds, 
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235     },
236 "server" => {
237     "ip"                    => [\$server_ip, "0.0.0.0"],
238     "port"                  => [\$server_port, "20081"],
239     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
249     "repo-path"             => [\$repo_path, '/srv/www/repository'],
250     "ldap-uri"              => [\$ldap_uri, ""],
251     "ldap-base"             => [\$ldap_base, ""],
252     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
253     "ldap-admin-password"   => [\$ldap_admin_password, ""],
254     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
255     "max-clients"           => [\$max_clients, 10],
256     "wol-password"          => [\$wake_on_lan_passwd, ""],
257                 "mysql-username"        => [\$mysql_username, "gosa_si"],
258                 "mysql-password"        => [\$mysql_password, ""],
259                 "mysql-database"        => [\$mysql_database, "gosa_si"],
260                 "mysql-host"            => [\$mysql_host, "127.0.0.1"],
261     },
262 "GOsaPackages" => {
263     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266     "key" => [\$GosaPackages_key, "none"],
267                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268     },
269 "ClientPackages" => {
270     "key" => [\$ClientPackages_key, "none"],
271     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272     },
273 "ServerPackages"=> {
274     "address"      => [\$foreign_server_string, ""],
275     "dns-lookup"            => [\$dns_lookup, "true"],
276     "domain"  => [\$server_domain, ""],
277     "key"     => [\$ServerPackages_key, "none"],
278     "key-lifetime" => [\$foreign_servers_register_delay, 120],
279     "job-synchronization-enabled" => [\$job_synchronization, "true"],
280     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281     },
282 "ArpHandler" => {
283     "enabled"   => [\$arp_enabled, "true"],
284     "interface" => [\$arp_interface, "all"],
285         },
286 "Opsi" => {
287     "enabled"  => [\$opsi_enabled, "false"], 
288     "server"   => [\$opsi_server, "localhost"],
289     "admin"    => [\$opsi_admin, "opsi-admin"],
290     "password" => [\$opsi_password, "secret"],
291    },
293 );
296 #===  FUNCTION  ================================================================
297 #         NAME:  usage
298 #   PARAMETERS:  nothing
299 #      RETURNS:  nothing
300 #  DESCRIPTION:  print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303     print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306            -h        : this (help) message
307            -c <file> : config file
308            -f        : foreground, process will not be forked to background
309            -v        : be verbose (multiple to increase verbosity)
310            -no-arp   : starts $prg without connection to arp module
311  
312 EOF
313     print "\n" ;
317 #===  FUNCTION  ================================================================
318 #         NAME:  logging
319 #   PARAMETERS:  level - string - default 'info'
320 #                msg - string -
321 #                facility - string - default 'LOG_DAEMON'
322 #      RETURNS:  nothing
323 #  DESCRIPTION:  function for logging
324 #===============================================================================
325 sub daemon_log {
326     # log into log_file
327     my( $msg, $level ) = @_;
328     if(not defined $msg) { return }
329     if(not defined $level) { $level = 1 }
330     if(defined $log_file){
331         open(LOG_HANDLE, ">>$log_file");
332         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333             print STDERR "cannot open $log_file: $!";
334             return 
335         }
336         chomp($msg);
337         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
338         if($level <= $verbose){
339             my ($seconds, $minutes, $hours, $monthday, $month,
340                     $year, $weekday, $yearday, $sommertime) = localtime(time);
341             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345             $month = $monthnames[$month];
346             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347             $year+=1900;
348             my $name = $prg;
350             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351             print LOG_HANDLE $log_msg;
352             if( $foreground ) { 
353                 print STDERR $log_msg;
354             }
355         }
356         close( LOG_HANDLE );
357     }
361 #===  FUNCTION  ================================================================
362 #         NAME:  check_cmdline_param
363 #   PARAMETERS:  nothing
364 #      RETURNS:  nothing
365 #  DESCRIPTION:  validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368     my $err_config;
369     my $err_counter = 0;
370         if(not defined($cfg_file)) {
371                 $cfg_file = "/etc/gosa-si/server.conf";
372                 if(! -r $cfg_file) {
373                         $err_config = "please specify a config file";
374                         $err_counter += 1;
375                 }
376     }
377     if( $err_counter > 0 ) {
378         &usage( "", 1 );
379         if( defined( $err_config)) { print STDERR "$err_config\n"}
380         print STDERR "\n";
381         exit( -1 );
382     }
386 #===  FUNCTION  ================================================================
387 #         NAME:  check_pid
388 #   PARAMETERS:  nothing
389 #      RETURNS:  nothing
390 #  DESCRIPTION:  handels pid processing
391 #===============================================================================
392 sub check_pid {
393     $pid = -1;
394     # Check, if we are already running
395     if( open(LOCK_FILE, "<$pid_file") ) {
396         $pid = <LOCK_FILE>;
397         if( defined $pid ) {
398             chomp( $pid );
399             if( -f "/proc/$pid/stat" ) {
400                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401                 if( $stat ) {
402                                         daemon_log("ERROR: Already running",1);
403                     close( LOCK_FILE );
404                     exit -1;
405                 }
406             }
407         }
408         close( LOCK_FILE );
409         unlink( $pid_file );
410     }
412     # create a syslog msg if it is not to possible to open PID file
413     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415         if (open(LOCK_FILE, '<', $pid_file)
416                 && ($pid = <LOCK_FILE>))
417         {
418             chomp($pid);
419             $msg .= "(PID $pid)\n";
420         } else {
421             $msg .= "(unable to read PID)\n";
422         }
423         if( ! ($foreground) ) {
424             openlog( $0, "cons,pid", "daemon" );
425             syslog( "warning", $msg );
426             closelog();
427         }
428         else {
429             print( STDERR " $msg " );
430         }
431         exit( -1 );
432     }
435 #===  FUNCTION  ================================================================
436 #         NAME:  import_modules
437 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
438 #                are stored
439 #      RETURNS:  nothing
440 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
441 #                state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444     daemon_log(" ", 1);
446     if (not -e $modules_path) {
447         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
448     }
450     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451     while (defined (my $file = readdir (DIR))) {
452         if (not $file =~ /(\S*?).pm$/) {
453             next;
454         }
455                 my $mod_name = $1;
457         # ArpHandler switch
458         if( $file =~ /ArpHandler.pm/ ) {
459             if( $arp_enabled eq "false" ) { next; }
460         }
461         
462         eval { require $file; };
463         if ($@) {
464             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465             daemon_log("$@", 1);
466             exit;
467                 } else {
468                         my $info = eval($mod_name.'::get_module_info()');
469                         # Only load module if get_module_info() returns a non-null object
470                         if( $info ) {
471                                 my ($input_address, $input_key, $event_hash) = @{$info};
472                                 $known_modules->{$mod_name} = $info;
473                                 daemon_log("0 INFO: module $mod_name loaded", 5);
474                         }
475                 }
476     }   
478     close (DIR);
481 #===  FUNCTION  ================================================================
482 #         NAME:  password_check
483 #   PARAMETERS:  nothing
484 #      RETURNS:  nothing
485 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
486 #                the same password
487 #===============================================================================
488 sub password_check {
489     my $passwd_hash = {};
490     while (my ($mod_name, $mod_info) = each %$known_modules) {
491         my $mod_passwd = @$mod_info[1];
492         if (not defined $mod_passwd) { next; }
493         if (not exists $passwd_hash->{$mod_passwd}) {
494             $passwd_hash->{$mod_passwd} = $mod_name;
496         # escalates critical error
497         } else {
498             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500             exit( -1 );
501         }
502     }
507 #===  FUNCTION  ================================================================
508 #         NAME:  sig_int_handler
509 #   PARAMETERS:  signal - string - signal arose from system
510 #      RETURNS:  nothing
511 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514     my ($signal) = @_;
516 #       if (defined($ldap_handle)) {
517 #               $ldap_handle->disconnect;
518 #       }
519     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
520     
522     daemon_log("shutting down gosa-si-server", 1);
523     system("kill `ps -C gosa-si-server -o pid=`");
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529     my ($crypted_msg, $module_key, $session_id) = @_;
530     my $msg;
531     my $msg_hash;
532     my $error_string;
533     eval{
534         $msg = &decrypt_msg($crypted_msg, $module_key);
536         if ($msg =~ /<xml>/i){
537             $msg =~ s/\s+/ /g;  # just for better daemon_log
538             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541             ##############
542             # check header
543             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544             my $header_l = $msg_hash->{'header'};
545             if( 1 > @{$header_l} ) { die 'empty header tag'; }
546             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547             my $header = @{$header_l}[0];
548             if( 0 == length $header) { die 'empty string in header tag'; }
550             ##############
551             # check source
552             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553             my $source_l = $msg_hash->{'source'};
554             if( 1 > @{$source_l} ) { die 'empty source tag'; }
555             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556             my $source = @{$source_l}[0];
557             if( 0 == length $source) { die 'source error'; }
559             ##############
560             # check target
561             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562             my $target_l = $msg_hash->{'target'};
563             if( 1 > @{$target_l} ) { die 'empty target tag'; }
564         }
565     };
566     if($@) {
567         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568         $msg = undef;
569         $msg_hash = undef;
570     }
572     return ($msg, $msg_hash);
576 sub check_outgoing_xml_validity {
577     my ($msg, $session_id) = @_;
579     my $msg_hash;
580     eval{
581         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583         ##############
584         # check header
585         my $header_l = $msg_hash->{'header'};
586         if( 1 != @{$header_l} ) {
587             die 'no or more than one headers specified';
588         }
589         my $header = @{$header_l}[0];
590         if( 0 == length $header) {
591             die 'header has length 0';
592         }
594         ##############
595         # check source
596         my $source_l = $msg_hash->{'source'};
597         if( 1 != @{$source_l} ) {
598             die 'no or more than 1 sources specified';
599         }
600         my $source = @{$source_l}[0];
601         if( 0 == length $source) {
602             die 'source has length 0';
603         }
604         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
605                 $source =~ /^GOSA$/i ) {
606             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
607         }
608         
609         ##############
610         # check target  
611         my $target_l = $msg_hash->{'target'};
612         if( 0 == @{$target_l} ) {
613             die "no targets specified";
614         }
615         foreach my $target (@$target_l) {
616             if( 0 == length $target) {
617                 die "target has length 0";
618             }
619             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
620                     $target =~ /^GOSA$/i ||
621                     $target =~ /^\*$/ ||
622                     $target =~ /KNOWN_SERVER/i ||
623                     $target =~ /JOBDB/i ||
624                     $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 ){
625                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
626             }
627         }
628     };
629     if($@) {
630         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
631         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
632         $msg_hash = undef;
633     }
635     return ($msg_hash);
639 sub input_from_known_server {
640     my ($input, $remote_ip, $session_id) = @_ ;  
641     my ($msg, $msg_hash, $module);
643     my $sql_statement= "SELECT * FROM known_server";
644     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
646     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
647         my $host_name = $hit->{hostname};
648         if( not $host_name =~ "^$remote_ip") {
649             next;
650         }
651         my $host_key = $hit->{hostkey};
652         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
653         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
655         # check if module can open msg envelope with module key
656         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
657         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
658             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
659             daemon_log("$@", 8);
660             next;
661         }
662         else {
663             $msg = $tmp_msg;
664             $msg_hash = $tmp_msg_hash;
665             $module = "ServerPackages";
666             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
667             last;
668         }
669     }
671     if( (!$msg) || (!$msg_hash) || (!$module) ) {
672         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
673     }
674   
675     return ($msg, $msg_hash, $module);
679 sub input_from_known_client {
680     my ($input, $remote_ip, $session_id) = @_ ;  
681     my ($msg, $msg_hash, $module);
683     my $sql_statement= "SELECT * FROM known_clients";
684     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
685     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
686         my $host_name = $hit->{hostname};
687         if( not $host_name =~ /^$remote_ip:\d*$/) {
688                 next;
689                 }
690         my $host_key = $hit->{hostkey};
691         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
692         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
694         # check if module can open msg envelope with module key
695         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
697         if( (!$msg) || (!$msg_hash) ) {
698             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
699             &daemon_log("$@", 8);
700             next;
701         }
702         else {
703             $module = "ClientPackages";
704             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
705             last;
706         }
707     }
709     if( (!$msg) || (!$msg_hash) || (!$module) ) {
710         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
711     }
713     return ($msg, $msg_hash, $module);
717 sub input_from_unknown_host {
718         no strict "refs";
719         my ($input, $session_id) = @_ ;
720         my ($msg, $msg_hash, $module);
721         my $error_string;
723         my %act_modules = %$known_modules;
725         while( my ($mod, $info) = each(%act_modules)) {
727                 # check a key exists for this module
728                 my $module_key = ${$mod."_key"};
729                 if( not defined $module_key ) {
730                         if( $mod eq 'ArpHandler' ) {
731                                 next;
732                         }
733                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
734                         next;
735                 }
736                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
738                 # check if module can open msg envelope with module key
739                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
740                 if( (not defined $msg) || (not defined $msg_hash) ) {
741                         next;
742                 } else {
743                         $module = $mod;
744             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
745                         last;
746                 }
747         }
749         if( (!$msg) || (!$msg_hash) || (!$module)) {
750                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
751         }
753         return ($msg, $msg_hash, $module);
757 sub create_ciphering {
758     my ($passwd) = @_;
759         if((!defined($passwd)) || length($passwd)==0) {
760                 $passwd = "";
761         }
762     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
763     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
764     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
765     $my_cipher->set_iv($iv);
766     return $my_cipher;
770 sub encrypt_msg {
771     my ($msg, $key) = @_;
772     my $my_cipher = &create_ciphering($key);
773     my $len;
774     {
775             use bytes;
776             $len= 16-length($msg)%16;
777     }
778     $msg = "\0"x($len).$msg;
779     $msg = $my_cipher->encrypt($msg);
780     chomp($msg = &encode_base64($msg));
781     # there are no newlines allowed inside msg
782     $msg=~ s/\n//g;
783     return $msg;
787 sub decrypt_msg {
789     my ($msg, $key) = @_ ;
790     $msg = &decode_base64($msg);
791     my $my_cipher = &create_ciphering($key);
792     $msg = $my_cipher->decrypt($msg); 
793     $msg =~ s/\0*//g;
794     return $msg;
798 sub get_encrypt_key {
799     my ($target) = @_ ;
800     my $encrypt_key;
801     my $error = 0;
803     # target can be in known_server
804     if( not defined $encrypt_key ) {
805         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
806         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
807         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
808             my $host_name = $hit->{hostname};
809             if( $host_name ne $target ) {
810                 next;
811             }
812             $encrypt_key = $hit->{hostkey};
813             last;
814         }
815     }
817     # target can be in known_client
818     if( not defined $encrypt_key ) {
819         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
820         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
821         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
822             my $host_name = $hit->{hostname};
823             if( $host_name ne $target ) {
824                 next;
825             }
826             $encrypt_key = $hit->{hostkey};
827             last;
828         }
829     }
831     return $encrypt_key;
835 #===  FUNCTION  ================================================================
836 #         NAME:  open_socket
837 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
838 #                [PeerPort] string necessary if port not appended by PeerAddr
839 #      RETURNS:  socket IO::Socket::INET
840 #  DESCRIPTION:  open a socket to PeerAddr
841 #===============================================================================
842 sub open_socket {
843     my ($PeerAddr, $PeerPort) = @_ ;
844     if(defined($PeerPort)){
845         $PeerAddr = $PeerAddr.":".$PeerPort;
846     }
847     my $socket;
848     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
849             Porto => "tcp",
850             Type => SOCK_STREAM,
851             Timeout => 5,
852             );
853     if(not defined $socket) {
854         return;
855     }
856 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
857     return $socket;
861 #sub get_local_ip_for_remote_ip {
862 #       my $remote_ip= shift;
863 #       my $result="0.0.0.0";
865 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
866 #               if($remote_ip eq "127.0.0.1") {
867 #                       $result = "127.0.0.1";
868 #               } else {
869 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
871 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
872 #                               or die "Could not open $PROC_NET_ROUTE";
874 #                       my @ifs = <PROC_NET_ROUTE>;
876 #                       close(PROC_NET_ROUTE);
878 #                       # Eat header line
879 #                       shift @ifs;
880 #                       chomp @ifs;
881 #                       foreach my $line(@ifs) {
882 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
883 #                               my $destination;
884 #                               my $mask;
885 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
886 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
887 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
888 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
889 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
890 #                                       # destination matches route, save mac and exit
891 #                                       $result= &get_ip($Iface);
892 #                                       last;
893 #                               }
894 #                       }
895 #               }
896 #       } else {
897 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
898 #       }
899 #       return $result;
900 #}
903 sub send_msg_to_target {
904     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
905     my $error = 0;
906     my $header;
907     my $timestamp = &get_time();
908     my $new_status;
909     my $act_status;
910     my ($sql_statement, $res);
911   
912     if( $msg_header ) {
913         $header = "'$msg_header'-";
914     } else {
915         $header = "";
916     }
918         # Patch the source ip
919         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
920                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
921                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
922         }
924     # encrypt xml msg
925     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
927     # opensocket
928     my $socket = &open_socket($address);
929     if( !$socket ) {
930         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
931         $error++;
932     }
933     
934     if( $error == 0 ) {
935         # send xml msg
936         print $socket $crypted_msg."\n";
938         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
939         daemon_log("$session_id DEBUG: message:\n$msg", 9);
940         
941     }
943     # close socket in any case
944     if( $socket ) {
945         close $socket;
946     }
948     if( $error > 0 ) { $new_status = "down"; }
949     else { $new_status = $msg_header; }
952     # known_clients
953     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
954     $res = $known_clients_db->select_dbentry($sql_statement);
955     if( keys(%$res) == 1) {
956         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
957         if ($act_status eq "down" && $new_status eq "down") {
958             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
959             $res = $known_clients_db->del_dbentry($sql_statement);
960             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
961         } else { 
962             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
963             $res = $known_clients_db->update_dbentry($sql_statement);
964             if($new_status eq "down"){
965                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
966             } else {
967                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
968             }
969         }
970     }
972     # known_server
973     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
974     $res = $known_server_db->select_dbentry($sql_statement);
975     if( keys(%$res) == 1) {
976         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
977         if ($act_status eq "down" && $new_status eq "down") {
978             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
979             $res = $known_server_db->del_dbentry($sql_statement);
980             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
981         } 
982         else { 
983             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
984             $res = $known_server_db->update_dbentry($sql_statement);
985             if($new_status eq "down"){
986                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
987             } else {
988                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
989             }
990         }
991     }
992     return $error; 
996 sub update_jobdb_status_for_send_msgs {
997     my ($answer, $error) = @_;
998     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
999         my $jobdb_id = $1;
1000             
1001         # sending msg faild
1002         if( $error ) {
1003             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1004                 my $sql_statement = "UPDATE $job_queue_tn ".
1005                     "SET status='error', result='can not deliver msg, please consult log file' ".
1006                     "WHERE id=$jobdb_id";
1007                 my $res = $job_db->update_dbentry($sql_statement);
1008             }
1010         # sending msg was successful
1011         } else {
1012             my $sql_statement = "UPDATE $job_queue_tn ".
1013                 "SET status='done' ".
1014                 "WHERE id=$jobdb_id AND status='processed'";
1015             my $res = $job_db->update_dbentry($sql_statement);
1016         }
1017     }
1021 sub sig_handler {
1022         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1023         daemon_log("0 INFO got signal '$signal'", 1); 
1024         $kernel->sig_handled();
1025         return;
1029 sub msg_to_decrypt {
1030         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1031         my $session_id = $session->ID;
1032         my ($msg, $msg_hash, $module);
1033         my $error = 0;
1035         # hole neue msg aus @msgs_to_decrypt
1036         my $next_msg = shift @msgs_to_decrypt;
1038         # msg is from a new client or gosa
1039         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1041         # msg is from a gosa-si-server
1042         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1043                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1044         }
1045         # msg is from a gosa-si-client
1046         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1047                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1048         }
1049         # an error occurred
1050         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1051                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1052                 # could not understand a msg from its server the client cause a re-registering process
1053                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1054                         "' to cause a re-registering of the client if necessary", 3);
1055                 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1056                 my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1057                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1058                         my $host_name = $hit->{'hostname'};
1059                         my $host_key = $hit->{'hostkey'};
1060                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1061                         my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1062                         &update_jobdb_status_for_send_msgs($ping_msg, $error);
1063                 }
1064                 $error++;
1065         }
1068         my $header;
1069         my $target;
1070         my $source;
1071         my $done = 0;
1072         my $sql;
1073         my $res;
1075         # check whether this message should be processed here
1076         if ($error == 0) {
1077                 $header = @{$msg_hash->{'header'}}[0];
1078                 $target = @{$msg_hash->{'target'}}[0];
1079                 $source = @{$msg_hash->{'source'}}[0];
1080                 my $not_found_in_known_clients_db = 0;
1081                 my $not_found_in_known_server_db = 0;
1082                 my $not_found_in_foreign_clients_db = 0;
1083                 my $local_address;
1084                 my $local_mac;
1085                 my ($target_ip, $target_port) = split(':', $target);
1087                 # Determine the local ip address if target is an ip address
1088                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1089                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1090                 } else {
1091                         $local_address = $server_address;
1092                 }
1094                 # Determine the local mac address if target is a mac address
1095                 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) {
1096                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1097                         my $network_interface= &get_interface_for_ip($loc_ip);
1098                         $local_mac = &get_mac_for_interface($network_interface);
1099                 } else {
1100                         $local_mac = $server_mac_address;
1101                 }
1103                 # target and source is equal to GOSA -> process here
1104                 if (not $done) {
1105                         if ($target eq "GOSA" && $source eq "GOSA") {
1106                                 $done = 1;                    
1107                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1108                         }
1109                 }
1111                 # target is own address without forward_to_gosa-tag -> process here
1112                 if (not $done) {
1113                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1114                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1115                                 $done = 1;
1116                                 if ($source eq "GOSA") {
1117                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1118                                 }
1119                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1120                         }
1121                 }
1123                 # target is a client address in known_clients -> process here
1124                 if (not $done) {
1125                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1126                         $res = $known_clients_db->select_dbentry($sql);
1127                         if (keys(%$res) > 0) {
1128                                 $done = 1; 
1129                                 my $hostname = $res->{1}->{'hostname'};
1130                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1131                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1132                                 if ($source eq "GOSA") {
1133                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1134                                 }
1135                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1137                         } else {
1138                                 $not_found_in_known_clients_db = 1;
1139                         }
1140                 }
1142                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1143                 if (not $done) {
1144                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1145                         my $gosa_at;
1146                         my $gosa_session_id;
1147                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1148                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1149                                 if ($gosa_at ne $local_address) {
1150                                         $done = 1;
1151                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1152                                 }
1153                         }
1154                 }
1156                 # if message should be processed here -> add message to incoming_db
1157                 if ($done) {
1158                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1159                         # so gosa-si-server knows how to process this kind of messages
1160                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1161                                 $module = "GosaPackages";
1162                         }
1164                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1165                                         primkey=>[],
1166                                         headertag=>$header,
1167                                         targettag=>$target,
1168                                         xmlmessage=>&encode_base64($msg),
1169                                         timestamp=>&get_time,
1170                                         module=>$module,
1171                                         sessionid=>$session_id,
1172                                 } );
1174                 }
1176                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1177                 if (not $done) {
1178                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1179                         my $gosa_at;
1180                         my $gosa_session_id;
1181                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1182                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1183                                 if ($gosa_at eq $local_address) {
1184                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1185                                         if( defined $session_reference ) {
1186                                                 $heap = $session_reference->get_heap();
1187                                         }
1188                                         if(exists $heap->{'client'}) {
1189                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1190                                                 $heap->{'client'}->put($msg);
1191                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1192                                         }
1193                                         $done = 1;
1194                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1195                                 }
1196                         }
1198                 }
1200                 # target is a client address in foreign_clients -> forward to registration server
1201                 if (not $done) {
1202                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1203                         $res = $foreign_clients_db->select_dbentry($sql);
1204                         if (keys(%$res) > 0) {
1205                                 my $hostname = $res->{1}->{'hostname'};
1206                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1207                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1208                                 my $regserver = $res->{1}->{'regserver'};
1209                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1210                                 my $res = $known_server_db->select_dbentry($sql);
1211                                 if (keys(%$res) > 0) {
1212                                         my $regserver_key = $res->{1}->{'hostkey'};
1213                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1214                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1215                                         if ($source eq "GOSA") {
1216                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1217                                         }
1218                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1219                                 }
1220                                 $done = 1;
1221                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1222                         } else {
1223                                 $not_found_in_foreign_clients_db = 1;
1224                         }
1225                 }
1227                 # target is a server address -> forward to server
1228                 if (not $done) {
1229                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1230                         $res = $known_server_db->select_dbentry($sql);
1231                         if (keys(%$res) > 0) {
1232                                 my $hostkey = $res->{1}->{'hostkey'};
1234                                 if ($source eq "GOSA") {
1235                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1236                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1238                                 }
1240                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1241                                 $done = 1;
1242                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1243                         } else {
1244                                 $not_found_in_known_server_db = 1;
1245                         }
1246                 }
1249                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1250                 if ( $not_found_in_foreign_clients_db 
1251                         && $not_found_in_known_server_db
1252                         && $not_found_in_known_clients_db) {
1253                         &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1254                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1255                                         primkey=>[],
1256                                         headertag=>$header,
1257                                         targettag=>$target,
1258                                         xmlmessage=>&encode_base64($msg),
1259                                         timestamp=>&get_time,
1260                                         module=>$module,
1261                                         sessionid=>$session_id,
1262                                 } );
1263                         $done = 1;
1264                 }
1267                 if (not $done) {
1268                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1269                         if ($source eq "GOSA") {
1270                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1271                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1273                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1274                                 if( defined $session_reference ) {
1275                                         $heap = $session_reference->get_heap();
1276                                 }
1277                                 if(exists $heap->{'client'}) {
1278                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1279                                         $heap->{'client'}->put($error_msg);
1280                                 }
1281                         }
1282                 }
1284         }
1286         return;
1290 sub next_task {
1291     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1292     my $running_task = POE::Wheel::Run->new(
1293             Program => sub { process_task($session, $heap, $task) },
1294             StdioFilter => POE::Filter::Reference->new(),
1295             StdoutEvent  => "task_result",
1296             StderrEvent  => "task_debug",
1297             CloseEvent   => "task_done",
1298             );
1299     $heap->{task}->{ $running_task->ID } = $running_task;
1302 sub handle_task_result {
1303     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1304     my $client_answer = $result->{'answer'};
1305     if( $client_answer =~ s/session_id=(\d+)$// ) {
1306         my $session_id = $1;
1307         if( defined $session_id ) {
1308             my $session_reference = $kernel->ID_id_to_session($session_id);
1309             if( defined $session_reference ) {
1310                 $heap = $session_reference->get_heap();
1311             }
1312         }
1314         if(exists $heap->{'client'}) {
1315             $heap->{'client'}->put($client_answer);
1316         }
1317     }
1318     $kernel->sig(CHLD => "child_reap");
1321 sub handle_task_debug {
1322     my $result = $_[ARG0];
1323     print STDERR "$result\n";
1326 sub handle_task_done {
1327     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1328     delete $heap->{task}->{$task_id};
1331 sub process_task {
1332     no strict "refs";
1333     #CHECK: Not @_[...]?
1334     my ($session, $heap, $task) = @_;
1335     my $error = 0;
1336     my $answer_l;
1337     my ($answer_header, @answer_target_l, $answer_source);
1338     my $client_answer = "";
1340     # prepare all variables needed to process message
1341     #my $msg = $task->{'xmlmessage'};
1342     my $msg = &decode_base64($task->{'xmlmessage'});
1343     my $incoming_id = $task->{'id'};
1344     my $module = $task->{'module'};
1345     my $header =  $task->{'headertag'};
1346     my $session_id = $task->{'sessionid'};
1347                 my $msg_hash;
1348                 eval {
1349         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1350                 }; 
1351                 daemon_log("ERROR: XML failure '$@'") if ($@);
1352     my $source = @{$msg_hash->{'source'}}[0];
1353     
1354     # set timestamp of incoming client uptodate, so client will not 
1355     # be deleted from known_clients because of expiration
1356     my $act_time = &get_time();
1357     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1358     my $res = $known_clients_db->exec_statement($sql);
1360     ######################
1361     # process incoming msg
1362     if( $error == 0) {
1363         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1364         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1365         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1367         if ( 0 < @{$answer_l} ) {
1368             my $answer_str = join("\n", @{$answer_l});
1369             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1370                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1371             }
1372             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1373         } else {
1374             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1375         }
1377     }
1378     if( !$answer_l ) { $error++ };
1380     ########
1381     # answer
1382     if( $error == 0 ) {
1384         foreach my $answer ( @{$answer_l} ) {
1385             # check outgoing msg to xml validity
1386             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1387             if( not defined $answer_hash ) { next; }
1388             
1389             $answer_header = @{$answer_hash->{'header'}}[0];
1390             @answer_target_l = @{$answer_hash->{'target'}};
1391             $answer_source = @{$answer_hash->{'source'}}[0];
1393             # deliver msg to all targets 
1394             foreach my $answer_target ( @answer_target_l ) {
1396                 # targets of msg are all gosa-si-clients in known_clients_db
1397                 if( $answer_target eq "*" ) {
1398                     # answer is for all clients
1399                     my $sql_statement= "SELECT * FROM known_clients";
1400                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1401                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1402                         my $host_name = $hit->{hostname};
1403                         my $host_key = $hit->{hostkey};
1404                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1405                         &update_jobdb_status_for_send_msgs($answer, $error);
1406                     }
1407                 }
1409                 # targets of msg are all gosa-si-server in known_server_db
1410                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1411                     # answer is for all server in known_server
1412                     my $sql_statement= "SELECT * FROM $known_server_tn";
1413                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1414                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1415                         my $host_name = $hit->{hostname};
1416                         my $host_key = $hit->{hostkey};
1417                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1418                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1419                         &update_jobdb_status_for_send_msgs($answer, $error);
1420                     }
1421                 }
1423                 # target of msg is GOsa
1424                                 elsif( $answer_target eq "GOSA" ) {
1425                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1426                                         my $add_on = "";
1427                     if( defined $session_id ) {
1428                         $add_on = ".session_id=$session_id";
1429                     }
1430                     # answer is for GOSA and has to returned to connected client
1431                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1432                     $client_answer = $gosa_answer.$add_on;
1433                 }
1435                 # target of msg is job queue at this host
1436                 elsif( $answer_target eq "JOBDB") {
1437                     $answer =~ /<header>(\S+)<\/header>/;   
1438                     my $header;
1439                     if( defined $1 ) { $header = $1; }
1440                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1441                     &update_jobdb_status_for_send_msgs($answer, $error);
1442                 }
1444                 # Target of msg is a mac address
1445                 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 ) {
1446                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1447                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1448                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1449                     my $found_ip_flag = 0;
1450                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1451                         my $host_name = $hit->{hostname};
1452                         my $host_key = $hit->{hostkey};
1453                         $answer =~ s/$answer_target/$host_name/g;
1454                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1455                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1456                         &update_jobdb_status_for_send_msgs($answer, $error);
1457                         $found_ip_flag++ ;
1458                     }   
1459                     if ($found_ip_flag == 0) {
1460                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1461                         my $res = $foreign_clients_db->select_dbentry($sql);
1462                         while( my ($hit_num, $hit) = each %{ $res } ) {
1463                             my $host_name = $hit->{hostname};
1464                             my $reg_server = $hit->{regserver};
1465                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1466                             
1467                             # Fetch key for reg_server
1468                             my $reg_server_key;
1469                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1470                             my $res = $known_server_db->select_dbentry($sql);
1471                             if (exists $res->{1}) {
1472                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1473                             } else {
1474                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1475                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1476                                 $reg_server_key = undef;
1477                             }
1479                             # Send answer to server where client is registered
1480                             if (defined $reg_server_key) {
1481                                 $answer =~ s/$answer_target/$host_name/g;
1482                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1483                                 &update_jobdb_status_for_send_msgs($answer, $error);
1484                                 $found_ip_flag++ ;
1485                             }
1486                         }
1487                     }
1488                     if( $found_ip_flag == 0) {
1489                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1490                     }
1492                 # Answer is for one specific host   
1493                 } else {
1494                     # get encrypt_key
1495                     my $encrypt_key = &get_encrypt_key($answer_target);
1496                     if( not defined $encrypt_key ) {
1497                         # unknown target
1498                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1499                         next;
1500                     }
1501                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1502                     &update_jobdb_status_for_send_msgs($answer, $error);
1503                 }
1504             }
1505         }
1506     }
1508     my $filter = POE::Filter::Reference->new();
1509     my %result = ( 
1510             status => "seems ok to me",
1511             answer => $client_answer,
1512             );
1514     my $output = $filter->put( [ \%result ] );
1515     print @$output;
1520 sub session_start {
1521     my ($kernel) = $_[KERNEL];
1522     $global_kernel = $kernel;
1523     $kernel->yield('register_at_foreign_servers');
1524         $kernel->yield('create_fai_server_db', $fai_server_tn );
1525         $kernel->yield('create_fai_release_db', $fai_release_tn );
1526     $kernel->yield('watch_for_next_tasks');
1527         $kernel->sig(USR1 => "sig_handler");
1528         $kernel->sig(USR2 => "recreate_packages_db");
1529         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1530         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1531     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1532         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1533     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1534         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1535     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1537     # Start opsi check
1538     if ($opsi_enabled eq "true") {
1539         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1540     }
1545 sub watch_for_done_jobs {
1546     #CHECK: $heap for what?
1547     my ($kernel,$heap) = @_[KERNEL, HEAP];
1549     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1550         my $res = $job_db->select_dbentry( $sql_statement );
1552     while( my ($id, $hit) = each %{$res} ) {
1553         my $jobdb_id = $hit->{id};
1554         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1555         my $res = $job_db->del_dbentry($sql_statement); 
1556     }
1558     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1562 sub watch_for_opsi_jobs {
1563     my ($kernel) = $_[KERNEL];
1565     # 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 
1566     # opsi install job is to parse the xml message. There is still the correct header.
1567     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1568         my $res = $job_db->select_dbentry( $sql_statement );
1570     # Ask OPSI for an update of the running jobs
1571     while (my ($id, $hit) = each %$res ) {
1572         # Determine current parameters of the job
1573         my $hostId = $hit->{'plainname'};
1574         my $macaddress = $hit->{'macaddress'};
1575         my $progress = $hit->{'progress'};
1577         my $result= {};
1578         
1579         # For hosts, only return the products that are or get installed
1580         my $callobj;
1581         $callobj = {
1582             method  => 'getProductStates_hash',
1583             params  => [ $hostId ],
1584             id  => 1,
1585         };
1586         
1587         my $hres = $opsi_client->call($opsi_url, $callobj);
1588         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1589         if (not &check_opsi_res($hres)) {
1590             my $htmp= $hres->result->{$hostId};
1591         
1592             # Check state != not_installed or action == setup -> load and add
1593             my $products= 0;
1594             my $installed= 0;
1595             my $installing = 0;
1596             my $error= 0;  
1597             my @installed_list;
1598             my @error_list;
1599             my $act_status = "none";
1600             foreach my $product (@{$htmp}){
1602                 if ($product->{'installationStatus'} ne "not_installed" or
1603                         $product->{'actionRequest'} eq "setup"){
1605                     # Increase number of products for this host
1606                     $products++;
1607         
1608                     if ($product->{'installationStatus'} eq "failed"){
1609                         $result->{$product->{'productId'}}= "error";
1610                         unshift(@error_list, $product->{'productId'});
1611                         $error++;
1612                     }
1613                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1614                         $result->{$product->{'productId'}}= "installed";
1615                         unshift(@installed_list, $product->{'productId'});
1616                         $installed++;
1617                     }
1618                     if ($product->{'installationStatus'} eq "installing"){
1619                         $result->{$product->{'productId'}}= "installing";
1620                         $installing++;
1621                         $act_status = "installing - ".$product->{'productId'};
1622                     }
1623                 }
1624             }
1625         
1626             # Estimate "rough" progress, avoid division by zero
1627             if ($products == 0) {
1628                 $result->{'progress'}= 0;
1629             } else {
1630                 $result->{'progress'}= int($installed * 100 / $products);
1631             }
1633             # Set updates in job queue
1634             if ((not $error) && (not $installing) && ($installed)) {
1635                 $act_status = "installed - ".join(", ", @installed_list);
1636             }
1637             if ($error) {
1638                 $act_status = "error - ".join(", ", @error_list);
1639             }
1640             if ($progress ne $result->{'progress'} ) {
1641                 # Updating progress and result 
1642                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1643                 my $update_res = $job_db->update_dbentry($update_statement);
1644             }
1645             if ($progress eq 100) { 
1646                 # Updateing status
1647                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1648                 if ($error) {
1649                     $done_statement .= "status='error'";
1650                 } else {
1651                     $done_statement .= "status='done'";
1652                 }
1653                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1654                 my $done_res = $job_db->update_dbentry($done_statement);
1655             }
1658         }
1659     }
1661     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1665 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1666 sub watch_for_modified_jobs {
1667     my ($kernel,$heap) = @_[KERNEL, HEAP];
1669     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1670     my $res = $job_db->select_dbentry( $sql_statement );
1671     
1672     # if db contains no jobs which should be update, do nothing
1673     if (keys %$res != 0) {
1675         if ($job_synchronization  eq "true") {
1676             # make out of the db result a gosa-si message   
1677             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1678  
1679             # update all other SI-server
1680             &inform_all_other_si_server($update_msg);
1681         }
1683         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1684         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1685         $res = $job_db->update_dbentry($sql_statement);
1686     }
1688     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1692 sub watch_for_new_jobs {
1693         if($watch_for_new_jobs_in_progress == 0) {
1694                 $watch_for_new_jobs_in_progress = 1;
1695                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1697                 # check gosa job quaeue for jobs with executable timestamp
1698                 my $timestamp = &get_time();
1699                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1700                 my $res = $job_db->exec_statement( $sql_statement );
1702                 # Merge all new jobs that would do the same actions
1703                 my @drops;
1704                 my $hits;
1705                 foreach my $hit (reverse @{$res} ) {
1706                         my $macaddress= lc @{$hit}[8];
1707                         my $headertag= @{$hit}[5];
1708                         if(
1709                                 defined($hits->{$macaddress}) &&
1710                                 defined($hits->{$macaddress}->{$headertag}) &&
1711                                 defined($hits->{$macaddress}->{$headertag}[0])
1712                         ) {
1713                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1714                         }
1715                         $hits->{$macaddress}->{$headertag}= $hit;
1716                 }
1718                 # Delete new jobs with a matching job in state 'processing'
1719                 foreach my $macaddress (keys %{$hits}) {
1720                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1721                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1722                                 if(defined($jobdb_id)) {
1723                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1724                                         my $res = $job_db->exec_statement( $sql_statement );
1725                                         foreach my $hit (@{$res}) {
1726                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1727                                         }
1728                                 } else {
1729                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1730                                 }
1731                         }
1732                 }
1734                 # Commit deletion
1735                 $job_db->exec_statementlist(\@drops);
1737                 # Look for new jobs that could be executed
1738                 foreach my $macaddress (keys %{$hits}) {
1740                         # Look if there is an executing job
1741                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1742                         my $res = $job_db->exec_statement( $sql_statement );
1744                         # Skip new jobs for host if there is a processing job
1745                         if(defined($res) and defined @{$res}[0]) {
1746                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1747                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1748                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1749                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1750                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1751                                         if(defined($res_2) and defined @{$res_2}[0]) {
1752                                                 # Set status from goto-activation to 'waiting' and update timestamp
1753                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1754                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1755                                         }
1756                                 }
1757                                 next;
1758                         }
1760                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1761                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1762                                 if(defined($jobdb_id)) {
1763                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1765                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1766                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1767                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1769                                         # expect macaddress is unique!!!!!!
1770                                         my $target = $res_hash->{1}->{hostname};
1772                                         # change header
1773                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1775                                         # add sqlite_id
1776                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1778                                         $job_msg =~ /<header>(\S+)<\/header>/;
1779                                         my $header = $1 ;
1780                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1782                                         # update status in job queue to 'processing'
1783                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1784                                         my $res = $job_db->update_dbentry($sql_statement);
1785 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1787                                         # We don't want parallel processing
1788                                         last;
1789                                 }
1790                         }
1791                 }
1793                 $watch_for_new_jobs_in_progress = 0;
1794                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1795         }
1799 sub watch_for_new_messages {
1800     my ($kernel,$heap) = @_[KERNEL, HEAP];
1801     my @coll_user_msg;   # collection list of outgoing messages
1802     
1803     # check messaging_db for new incoming messages with executable timestamp
1804     my $timestamp = &get_time();
1805     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1806     my $res = $messaging_db->exec_statement( $sql_statement );
1807         foreach my $hit (@{$res}) {
1809         # create outgoing messages
1810         my $message_to = @{$hit}[3];
1811         # translate message_to to plain login name
1812         my @message_to_l = split(/,/, $message_to);  
1813                 my %receiver_h; 
1814                 foreach my $receiver (@message_to_l) {
1815                         if ($receiver =~ /^u_([\s\S]*)$/) {
1816                                 $receiver_h{$1} = 0;
1817                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1818                                 my $group_name = $1;
1819                                 # fetch all group members from ldap and add them to receiver hash
1820                                 my $ldap_handle = &get_ldap_handle();
1821                                 if (defined $ldap_handle) {
1822                                                 my $mesg = $ldap_handle->search(
1823                                                                                 base => $ldap_base,
1824                                                                                 scope => 'sub',
1825                                                                                 attrs => ['memberUid'],
1826                                                                                 filter => "cn=$group_name",
1827                                                                                 );
1828                                                 if ($mesg->count) {
1829                                                                 my @entries = $mesg->entries;
1830                                                                 foreach my $entry (@entries) {
1831                                                                                 my @receivers= $entry->get_value("memberUid");
1832                                                                                 foreach my $receiver (@receivers) { 
1833                                                                                                 $receiver_h{$1} = 0;
1834                                                                                 }
1835                                                                 }
1836                                                 } 
1837                                                 # translating errors ?
1838                                                 if ($mesg->code) {
1839                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1840                                                 }
1841                                 # ldap handle error ?           
1842                                 } else {
1843                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1844                                 }
1845                         } else {
1846                                 my $sbjct = &encode_base64(@{$hit}[1]);
1847                                 my $msg = &encode_base64(@{$hit}[7]);
1848                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1849                         }
1850                 }
1851                 my @receiver_l = keys(%receiver_h);
1853         my $message_id = @{$hit}[0];
1855         #add each outgoing msg to messaging_db
1856         my $receiver;
1857         foreach $receiver (@receiver_l) {
1858             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1859                 "VALUES ('".
1860                 $message_id."', '".    # id
1861                 @{$hit}[1]."', '".     # subject
1862                 @{$hit}[2]."', '".     # message_from
1863                 $receiver."', '".      # message_to
1864                 "none"."', '".         # flag
1865                 "out"."', '".          # direction
1866                 @{$hit}[6]."', '".     # delivery_time
1867                 @{$hit}[7]."', '".     # message
1868                 $timestamp."'".     # timestamp
1869                 ")";
1870             &daemon_log("M DEBUG: $sql_statement", 1);
1871             my $res = $messaging_db->exec_statement($sql_statement);
1872             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1873         }
1875         # set incoming message to flag d=deliverd
1876         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1877         &daemon_log("M DEBUG: $sql_statement", 7);
1878         $res = $messaging_db->update_dbentry($sql_statement);
1879         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1880     }
1882     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1883     return;
1886 sub watch_for_delivery_messages {
1887     my ($kernel, $heap) = @_[KERNEL, HEAP];
1889     # select outgoing messages
1890     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1891     #&daemon_log("0 DEBUG: $sql", 7);
1892     my $res = $messaging_db->exec_statement( $sql_statement );
1893     
1894     # build out msg for each    usr
1895     foreach my $hit (@{$res}) {
1896         my $receiver = @{$hit}[3];
1897         my $msg_id = @{$hit}[0];
1898         my $subject = @{$hit}[1];
1899         my $message = @{$hit}[7];
1901         # resolve usr -> host where usr is logged in
1902         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1903         #&daemon_log("0 DEBUG: $sql", 7);
1904         my $res = $login_users_db->exec_statement($sql);
1906         # reciver is logged in nowhere
1907         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1909                 my $send_succeed = 0;
1910                 foreach my $hit (@$res) {
1911                                 my $receiver_host = @$hit[0];
1912                 my $delivered2host = 0;
1913                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1915                                 # Looking for host in know_clients_db 
1916                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1917                                 my $res = $known_clients_db->exec_statement($sql);
1919                 # Host is known in known_clients_db
1920                 if (ref(@$res[0]) eq "ARRAY") {
1921                     my $receiver_key = @{@{$res}[0]}[2];
1922                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1923                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1924                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1925                     if ($error == 0 ) {
1926                         $send_succeed++ ;
1927                         $delivered2host++ ;
1928                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1929                     } else {
1930                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1931                     }
1932                 }
1933                 
1934                 # Message already send, do not need to do anything more, otherwise ...
1935                 if ($delivered2host) { next;}
1936     
1937                 # ...looking for host in foreign_clients_db
1938                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1939                 $res = $foreign_clients_db->exec_statement($sql);
1940   
1941                                 # Host is known in foreign_clients_db 
1942                                 if (ref(@$res[0]) eq "ARRAY") { 
1943                     my $registration_server = @{@{$res}[0]}[2];
1944                     
1945                     # Fetch encryption key for registration server
1946                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1947                     my $res = $known_server_db->exec_statement($sql);
1948                     if (ref(@$res[0]) eq "ARRAY") { 
1949                         my $registration_server_key = @{@{$res}[0]}[3];
1950                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1951                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1952                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1953                         if ($error == 0 ) {
1954                             $send_succeed++ ;
1955                             $delivered2host++ ;
1956                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1957                         } else {
1958                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1959                         }
1961                     } else {
1962                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1963                                 "registrated at server '$registration_server', ".
1964                                 "but no data available in known_server_db ", 1); 
1965                     }
1966                 }
1967                 
1968                 if (not $delivered2host) {
1969                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1970                 }
1971                 }
1973                 if ($send_succeed) {
1974                                 # set outgoing msg at db to deliverd
1975                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1976                                 my $res = $messaging_db->exec_statement($sql); 
1977                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1978                 } else {
1979             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1980         }
1981         }
1983     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1984     return;
1988 sub watch_for_done_messages {
1989     my ($kernel,$heap) = @_[KERNEL, HEAP];
1991     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1992     #&daemon_log("0 DEBUG: $sql", 7);
1993     my $res = $messaging_db->exec_statement($sql); 
1995     foreach my $hit (@{$res}) {
1996         my $msg_id = @{$hit}[0];
1998         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1999         #&daemon_log("0 DEBUG: $sql", 7); 
2000         my $res = $messaging_db->exec_statement($sql);
2002         # not all usr msgs have been seen till now
2003         if ( ref(@$res[0]) eq "ARRAY") { next; }
2004         
2005         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2006         #&daemon_log("0 DEBUG: $sql", 7);
2007         $res = $messaging_db->exec_statement($sql);
2008     
2009     }
2011     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2012     return;
2016 sub watch_for_old_known_clients {
2017     my ($kernel,$heap) = @_[KERNEL, HEAP];
2019     my $sql_statement = "SELECT * FROM $known_clients_tn";
2020     my $res = $known_clients_db->select_dbentry( $sql_statement );
2022     my $act_time = int(&get_time());
2024     while ( my ($hit_num, $hit) = each %$res) {
2025         my $expired_timestamp = int($hit->{'timestamp'});
2026         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2027         my $dt = DateTime->new( year   => $1,
2028                 month  => $2,
2029                 day    => $3,
2030                 hour   => $4,
2031                 minute => $5,
2032                 second => $6,
2033                 );
2035         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2036         $expired_timestamp = $dt->ymd('').$dt->hms('');
2037         if ($act_time > $expired_timestamp) {
2038             my $hostname = $hit->{'hostname'};
2039             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2040             my $del_res = $known_clients_db->exec_statement($del_sql);
2042             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2043         }
2045     }
2047     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2051 sub watch_for_next_tasks {
2052     my ($kernel,$heap) = @_[KERNEL, HEAP];
2054     my $sql = "SELECT * FROM $incoming_tn";
2055     my $res = $incoming_db->select_dbentry($sql);
2056     
2057     while ( my ($hit_num, $hit) = each %$res) {
2058         my $headertag = $hit->{'headertag'};
2059         if ($headertag =~ /^answer_(\d+)/) {
2060             # do not start processing, this message is for a still running POE::Wheel
2061             next;
2062         }
2063         my $message_id = $hit->{'id'};
2064         my $session_id = $hit->{'sessionid'};
2065         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2066         $kernel->yield('next_task', $hit);
2068         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2069         my $res = $incoming_db->exec_statement($sql);
2070     }
2072     $kernel->delay_set('watch_for_next_tasks', 1); 
2076 sub get_ldap_handle {
2077         my ($session_id) = @_;
2078         my $heap;
2079         my $ldap_handle;
2081         if (not defined $session_id ) { $session_id = 0 };
2082         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2084         if ($session_id == 0) {
2085                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2086                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2087                 if (defined $ldap_handle) {
2088                         $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!"); 
2089                 } else {
2090                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2091                 }
2093         } else {
2094                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2095                 if( defined $session_reference ) {
2096                         $heap = $session_reference->get_heap();
2097                 }
2099                 if (not defined $heap) {
2100                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2101                         return;
2102                 }
2104                 # TODO: This "if" is nonsense, because it doesn't prove that the
2105                 #       used handle is still valid - or if we've to reconnect...
2106                 #if (not exists $heap->{ldap_handle}) {
2107                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2108                         $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!"); 
2109                         $heap->{ldap_handle} = $ldap_handle;
2110                 #}
2111         }
2112         return $ldap_handle;
2116 sub change_fai_state {
2117     my ($st, $targets, $session_id) = @_;
2118     $session_id = 0 if not defined $session_id;
2119     # Set FAI state to localboot
2120     my %mapActions= (
2121         reboot    => '',
2122         update    => 'softupdate',
2123         localboot => 'localboot',
2124         reinstall => 'install',
2125         rescan    => '',
2126         wake      => '',
2127         memcheck  => 'memcheck',
2128         sysinfo   => 'sysinfo',
2129         install   => 'install',
2130     );
2132     # Return if this is unknown
2133     if (!exists $mapActions{ $st }){
2134         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2135       return;
2136     }
2138     my $state= $mapActions{ $st };
2140     my $ldap_handle = &get_ldap_handle($session_id);
2141     if( defined($ldap_handle) ) {
2143       # Build search filter for hosts
2144         my $search= "(&(objectClass=GOhard)";
2145         foreach (@{$targets}){
2146             $search.= "(macAddress=$_)";
2147         }
2148         $search.= ")";
2150       # If there's any host inside of the search string, procress them
2151         if (!($search =~ /macAddress/)){
2152             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2153             return;
2154         }
2156       # Perform search for Unit Tag
2157       my $mesg = $ldap_handle->search(
2158           base   => $ldap_base,
2159           scope  => 'sub',
2160           attrs  => ['dn', 'FAIstate', 'objectClass'],
2161           filter => "$search"
2162           );
2164           if ($mesg->count) {
2165                   my @entries = $mesg->entries;
2166                   if (0 == @entries) {
2167                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2168                   }
2170                   foreach my $entry (@entries) {
2171                           # Only modify entry if it is not set to '$state'
2172                           if ($entry->get_value("FAIstate") ne "$state"){
2173                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2174                                   my $result;
2175                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2176                                   if (exists $tmp{'FAIobject'}){
2177                                           if ($state eq ''){
2178                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2179                                                           delete => [ FAIstate => [] ] ]);
2180                                           } else {
2181                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2182                                                           replace => [ FAIstate => $state ] ]);
2183                                           }
2184                                   } elsif ($state ne ''){
2185                                           $result= $ldap_handle->modify($entry->dn, changes => [
2186                                                   add     => [ objectClass => 'FAIobject' ],
2187                                                   add     => [ FAIstate => $state ] ]);
2188                                   }
2190                                   # Errors?
2191                                   if ($result->code){
2192                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2193                                   }
2194                           } else {
2195                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2196                           }  
2197                   }
2198           } else {
2199                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2200           }
2202     # if no ldap handle defined
2203     } else {
2204         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2205     }
2207         return;
2211 sub change_goto_state {
2212     my ($st, $targets, $session_id) = @_;
2213     $session_id = 0  if not defined $session_id;
2215     # Switch on or off?
2216     my $state= $st eq 'active' ? 'active': 'locked';
2218     my $ldap_handle = &get_ldap_handle($session_id);
2219     if( defined($ldap_handle) ) {
2221       # Build search filter for hosts
2222       my $search= "(&(objectClass=GOhard)";
2223       foreach (@{$targets}){
2224         $search.= "(macAddress=$_)";
2225       }
2226       $search.= ")";
2228       # If there's any host inside of the search string, procress them
2229       if (!($search =~ /macAddress/)){
2230         return;
2231       }
2233       # Perform search for Unit Tag
2234       my $mesg = $ldap_handle->search(
2235           base   => $ldap_base,
2236           scope  => 'sub',
2237           attrs  => ['dn', 'gotoMode'],
2238           filter => "$search"
2239           );
2241       if ($mesg->count) {
2242         my @entries = $mesg->entries;
2243         foreach my $entry (@entries) {
2245           # Only modify entry if it is not set to '$state'
2246           if ($entry->get_value("gotoMode") ne $state){
2248             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2249             my $result;
2250             $result= $ldap_handle->modify($entry->dn, changes => [
2251                                                 replace => [ gotoMode => $state ] ]);
2253             # Errors?
2254             if ($result->code){
2255               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2256             }
2258           }
2259         }
2260       } else {
2261                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2262           }
2264     }
2268 sub run_recreate_packages_db {
2269     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2270     my $session_id = $session->ID;
2271         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2272         $kernel->yield('create_fai_release_db', $fai_release_tn);
2273         $kernel->yield('create_fai_server_db', $fai_server_tn);
2274         return;
2278 sub run_create_fai_server_db {
2279     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2280     my $session_id = $session->ID;
2281     my $task = POE::Wheel::Run->new(
2282             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2283             StdoutEvent  => "session_run_result",
2284             StderrEvent  => "session_run_debug",
2285             CloseEvent   => "session_run_done",
2286             );
2288     $heap->{task}->{ $task->ID } = $task;
2289     return;
2293 sub create_fai_server_db {
2294         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2295         my $result;
2297         if (not defined $session_id) { $session_id = 0; }
2298         my $ldap_handle = &get_ldap_handle();
2299         if(defined($ldap_handle)) {
2300                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2301                 my $mesg= $ldap_handle->search(
2302                         base   => $ldap_base,
2303                         scope  => 'sub',
2304                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2305                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2306                 );
2307                 if($mesg->{'resultCode'} == 0 &&
2308                         $mesg->count != 0) {
2309                         foreach my $entry (@{$mesg->{entries}}) {
2310                                 if($entry->exists('FAIrepository')) {
2311                                         # Add an entry for each Repository configured for server
2312                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2313                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2314                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2315                                                 $result= $fai_server_db->add_dbentry( { 
2316                                                                 table => $table_name,
2317                                                                 primkey => ['server', 'fai_release', 'tag'],
2318                                                                 server => $tmp_url,
2319                                                                 fai_release => $tmp_release,
2320                                                                 sections => $tmp_sections,
2321                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2322                                                         } );
2323                                         }
2324                                 }
2325                         }
2326                 }
2327                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2329                 # TODO: Find a way to post the 'create_packages_list_db' event
2330                 if(not defined($dont_create_packages_list)) {
2331                         &create_packages_list_db(undef, undef, $session_id);
2332                 }
2333         }       
2335         $ldap_handle->disconnect;
2336         return $result;
2340 sub run_create_fai_release_db {
2341         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2342         my $session_id = $session->ID;
2343         my $task = POE::Wheel::Run->new(
2344                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2345                 StdoutEvent  => "session_run_result",
2346                 StderrEvent  => "session_run_debug",
2347                 CloseEvent   => "session_run_done",
2348         );
2350         $heap->{task}->{ $task->ID } = $task;
2351         return;
2355 sub create_fai_release_db {
2356         my ($table_name, $session_id) = @_;
2357         my $result;
2359         # used for logging
2360         if (not defined $session_id) { $session_id = 0; }
2362         my $ldap_handle = &get_ldap_handle();
2363         if(defined($ldap_handle)) {
2364                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2365                 my $mesg= $ldap_handle->search(
2366                         base   => $ldap_base,
2367                         scope  => 'sub',
2368                         attrs  => [],
2369                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2370                 );
2371                 if($mesg->{'resultCode'} == 0 &&
2372                         $mesg->count != 0) {
2373                         # Walk through all possible FAI container ou's
2374                         my @sql_list;
2375                         my $timestamp= &get_time();
2376                         foreach my $ou (@{$mesg->{entries}}) {
2377                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2378                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2379                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2380                                         if(@tmp_array) {
2381                                                 foreach my $entry (@tmp_array) {
2382                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2383                                                                 my $sql= 
2384                                                                 "INSERT INTO $table_name "
2385                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2386                                                                 .$timestamp.","
2387                                                                 ."'".$entry->{'release'}."',"
2388                                                                 ."'".$entry->{'class'}."',"
2389                                                                 ."'".$entry->{'type'}."',"
2390                                                                 ."'".$entry->{'state'}."')";
2391                                                                 push @sql_list, $sql;
2392                                                         }
2393                                                 }
2394                                         }
2395                                 }
2396                         }
2398                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2399                         if(@sql_list) {
2400                                 unshift @sql_list, "DELETE FROM $table_name";
2401                                 $fai_release_db->exec_statementlist(\@sql_list);
2402                         }
2403                         daemon_log("$session_id DEBUG: Done with inserting",7);
2404                 }
2405                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2406         }
2407         $ldap_handle->disconnect;
2408         return $result;
2411 sub get_fai_types {
2412         my $tmp_classes = shift || return undef;
2413         my @result;
2415         foreach my $type(keys %{$tmp_classes}) {
2416                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2417                         my $entry = {
2418                                 type => $type,
2419                                 state => $tmp_classes->{$type}[0],
2420                         };
2421                         push @result, $entry;
2422                 }
2423         }
2425         return @result;
2428 sub get_fai_state {
2429         my $result = "";
2430         my $tmp_classes = shift || return $result;
2432         foreach my $type(keys %{$tmp_classes}) {
2433                 if(defined($tmp_classes->{$type}[0])) {
2434                         $result = $tmp_classes->{$type}[0];
2435                         
2436                 # State is equal for all types in class
2437                         last;
2438                 }
2439         }
2441         return $result;
2444 sub resolve_fai_classes {
2445         my ($fai_base, $ldap_handle, $session_id) = @_;
2446         if (not defined $session_id) { $session_id = 0; }
2447         my $result;
2448         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2449         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2450         my $fai_classes;
2452         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2453         my $mesg= $ldap_handle->search(
2454                 base   => $fai_base,
2455                 scope  => 'sub',
2456                 attrs  => ['cn','objectClass','FAIstate'],
2457                 filter => $fai_filter,
2458         );
2459         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2461         if($mesg->{'resultCode'} == 0 &&
2462                 $mesg->count != 0) {
2463                 foreach my $entry (@{$mesg->{entries}}) {
2464                         if($entry->exists('cn')) {
2465                                 my $tmp_dn= $entry->dn();
2467                                 # Skip classname and ou dn parts for class
2468                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2470                                 # Skip classes without releases
2471                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2472                                         next;
2473                                 }
2475                                 my $tmp_cn= $entry->get_value('cn');
2476                                 my $tmp_state= $entry->get_value('FAIstate');
2478                                 my $tmp_type;
2479                                 # Get FAI type
2480                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2481                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2482                                                 $tmp_type= $oclass;
2483                                                 last;
2484                                         }
2485                                 }
2487                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2488                                         # A Subrelease
2489                                         my @sub_releases = split(/,/, $tmp_release);
2491                                         # Walk through subreleases and build hash tree
2492                                         my $hash;
2493                                         while(my $tmp_sub_release = pop @sub_releases) {
2494                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2495                                         }
2496                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2497                                 } else {
2498                                         # A branch, no subrelease
2499                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2500                                 }
2501                         } elsif (!$entry->exists('cn')) {
2502                                 my $tmp_dn= $entry->dn();
2503                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2505                                 # Skip classes without releases
2506                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2507                                         next;
2508                                 }
2510                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2511                                         # A Subrelease
2512                                         my @sub_releases= split(/,/, $tmp_release);
2514                                         # Walk through subreleases and build hash tree
2515                                         my $hash;
2516                                         while(my $tmp_sub_release = pop @sub_releases) {
2517                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2518                                         }
2519                                         # Remove the last two characters
2520                                         chop($hash);
2521                                         chop($hash);
2523                                         eval('$fai_classes->'.$hash.'= {}');
2524                                 } else {
2525                                         # A branch, no subrelease
2526                                         if(!exists($fai_classes->{$tmp_release})) {
2527                                                 $fai_classes->{$tmp_release} = {};
2528                                         }
2529                                 }
2530                         }
2531                 }
2533                 # The hash is complete, now we can honor the copy-on-write based missing entries
2534                 foreach my $release (keys %$fai_classes) {
2535                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2536                 }
2537         }
2538         return $result;
2541 sub apply_fai_inheritance {
2542        my $fai_classes = shift || return {};
2543        my $tmp_classes;
2545        # Get the classes from the branch
2546        foreach my $class (keys %{$fai_classes}) {
2547                # Skip subreleases
2548                if($class =~ /^ou=.*$/) {
2549                        next;
2550                } else {
2551                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2552                }
2553        }
2555        # Apply to each subrelease
2556        foreach my $subrelease (keys %{$fai_classes}) {
2557                if($subrelease =~ /ou=/) {
2558                        foreach my $tmp_class (keys %{$tmp_classes}) {
2559                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2560                                        $fai_classes->{$subrelease}->{$tmp_class} =
2561                                        deep_copy($tmp_classes->{$tmp_class});
2562                                } else {
2563                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2564                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2565                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2566                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2567                                                }
2568                                        }
2569                                }
2570                        }
2571                }
2572        }
2574        # Find subreleases in deeper levels
2575        foreach my $subrelease (keys %{$fai_classes}) {
2576                if($subrelease =~ /ou=/) {
2577                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2578                                if($subsubrelease =~ /ou=/) {
2579                                        apply_fai_inheritance($fai_classes->{$subrelease});
2580                                }
2581                        }
2582                }
2583        }
2585        return $fai_classes;
2588 sub get_fai_release_entries {
2589         my $tmp_classes = shift || return;
2590         my $parent = shift || "";
2591         my @result = shift || ();
2593         foreach my $entry (keys %{$tmp_classes}) {
2594                 if(defined($entry)) {
2595                         if($entry =~ /^ou=.*$/) {
2596                                 my $release_name = $entry;
2597                                 $release_name =~ s/ou=//g;
2598                                 if(length($parent)>0) {
2599                                         $release_name = $parent."/".$release_name;
2600                                 }
2601                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2602                                 foreach my $bufentry(@bufentries) {
2603                                         push @result, $bufentry;
2604                                 }
2605                         } else {
2606                                 my @types = get_fai_types($tmp_classes->{$entry});
2607                                 foreach my $type (@types) {
2608                                         push @result, 
2609                                         {
2610                                                 'class' => $entry,
2611                                                 'type' => $type->{'type'},
2612                                                 'release' => $parent,
2613                                                 'state' => $type->{'state'},
2614                                         };
2615                                 }
2616                         }
2617                 }
2618         }
2620         return @result;
2623 sub deep_copy {
2624         my $this = shift;
2625         if (not ref $this) {
2626                 $this;
2627         } elsif (ref $this eq "ARRAY") {
2628                 [map deep_copy($_), @$this];
2629         } elsif (ref $this eq "HASH") {
2630                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2631         } else { die "what type is $_?" }
2635 sub session_run_result {
2636     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2637     $kernel->sig(CHLD => "child_reap");
2640 sub session_run_debug {
2641     my $result = $_[ARG0];
2642     print STDERR "$result\n";
2645 sub session_run_done {
2646     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2647     delete $heap->{task}->{$task_id};
2651 sub create_sources_list {
2652         my $session_id = shift;
2653         my $ldap_handle = &main::get_ldap_handle;
2654         my $result="/tmp/gosa_si_tmp_sources_list";
2656         # Remove old file
2657         if(stat($result)) {
2658                 unlink($result);
2659                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2660         }
2662         my $fh;
2663         open($fh, ">$result");
2664         if (not defined $fh) {
2665                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2666                 return undef;
2667         }
2668         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2669                 my $mesg=$ldap_handle->search(
2670                         base    => $main::ldap_server_dn,
2671                         scope   => 'base',
2672                         attrs   => 'FAIrepository',
2673                         filter  => 'objectClass=FAIrepositoryServer'
2674                 );
2675                 if($mesg->count) {
2676                         foreach my $entry(@{$mesg->{'entries'}}) {
2677                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2678                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2679                                         my $line = "deb $server $release";
2680                                         $sections =~ s/,/ /g;
2681                                         $line.= " $sections";
2682                                         print $fh $line."\n";
2683                                 }
2684                         }
2685                 }
2686         } else {
2687                 if (defined $main::ldap_server_dn){
2688                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2689                 } else {
2690                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2691                 }
2692         }
2693         close($fh);
2695         return $result;
2699 sub run_create_packages_list_db {
2700     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2701         my $session_id = $session->ID;
2703         my $task = POE::Wheel::Run->new(
2704                                         Priority => +20,
2705                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2706                                         StdoutEvent  => "session_run_result",
2707                                         StderrEvent  => "session_run_debug",
2708                                         CloseEvent   => "session_run_done",
2709                                         );
2710         $heap->{task}->{ $task->ID } = $task;
2714 sub create_packages_list_db {
2715         my ($ldap_handle, $sources_file, $session_id) = @_;
2716         
2717         # it should not be possible to trigger a recreation of packages_list_db
2718         # while packages_list_db is under construction, so set flag packages_list_under_construction
2719         # which is tested befor recreation can be started
2720         if (-r $packages_list_under_construction) {
2721                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2722                 return;
2723         } else {
2724                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2725                 # set packages_list_under_construction to true
2726                 system("touch $packages_list_under_construction");
2727                 @packages_list_statements=();
2728         }
2730         if (not defined $session_id) { $session_id = 0; }
2731         if (not defined $ldap_handle) { 
2732                 $ldap_handle= &get_ldap_handle();
2734                 if (not defined $ldap_handle) {
2735                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2736                         unlink($packages_list_under_construction);
2737                         return;
2738                 }
2739         }
2740         if (not defined $sources_file) { 
2741                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2742                 $sources_file = &create_sources_list($session_id);
2743         }
2745         if (not defined $sources_file) {
2746                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2747                 unlink($packages_list_under_construction);
2748                 return;
2749         }
2751         my $line;
2753         open(CONFIG, "<$sources_file") or do {
2754                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2755                 unlink($packages_list_under_construction);
2756                 return;
2757         };
2759         # Read lines
2760         while ($line = <CONFIG>){
2761                 # Unify
2762                 chop($line);
2763                 $line =~ s/^\s+//;
2764                 $line =~ s/^\s+/ /;
2766                 # Strip comments
2767                 $line =~ s/#.*$//g;
2769                 # Skip empty lines
2770                 if ($line =~ /^\s*$/){
2771                         next;
2772                 }
2774                 # Interpret deb line
2775                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2776                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2777                         my $section;
2778                         foreach $section (split(' ', $sections)){
2779                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2780                         }
2781                 }
2782         }
2784         close (CONFIG);
2787         if(keys(%repo_dirs)) {
2788                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2789                 &main::strip_packages_list_statements();
2790                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2791         }
2792         unlink($packages_list_under_construction);
2793         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2794         return;
2797 # This function should do some intensive task to minimize the db-traffic
2798 sub strip_packages_list_statements {
2799     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2800         my @new_statement_list=();
2801         my $hash;
2802         my $insert_hash;
2803         my $update_hash;
2804         my $delete_hash;
2805         my $local_timestamp=get_time();
2807         foreach my $existing_entry (@existing_entries) {
2808                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2809         }
2811         foreach my $statement (@packages_list_statements) {
2812                 if($statement =~ /^INSERT/i) {
2813                         # Assign the values from the insert statement
2814                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2815                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2816                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2817                                 # If section or description has changed, update the DB
2818                                 if( 
2819                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2820                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2821                                 ) {
2822                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2823                                 }
2824                         } else {
2825                                 # Insert a non-existing entry to db
2826                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2827                         }
2828                 } elsif ($statement =~ /^UPDATE/i) {
2829                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2830                         /^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;
2831                         foreach my $distribution (keys %{$hash}) {
2832                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2833                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2834                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2835                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2836                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2837                                                 my $section;
2838                                                 my $description;
2839                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2840                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2841                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2842                                                 }
2843                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2844                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2845                                                 }
2846                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2847                                         }
2848                                 }
2849                         }
2850                 }
2851         }
2853         # TODO: Check for orphaned entries
2855         # unroll the insert_hash
2856         foreach my $distribution (keys %{$insert_hash}) {
2857                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2858                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2859                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2860                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2861                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2862                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2863                                 ."'$local_timestamp')";
2864                         }
2865                 }
2866         }
2868         # unroll the update hash
2869         foreach my $distribution (keys %{$update_hash}) {
2870                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2871                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2872                                 my $set = "";
2873                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2874                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2875                                 }
2876                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2877                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2878                                 }
2879                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2880                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2881                                 }
2882                                 if(defined($set) and length($set) > 0) {
2883                                         $set .= "timestamp = '$local_timestamp'";
2884                                 } else {
2885                                         next;
2886                                 }
2887                                 push @new_statement_list, 
2888                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2889                                         ." distribution = '$distribution'"
2890                                         ." AND package = '$package'"
2891                                         ." AND version = '$version'";
2892                         }
2893                 }
2894         }
2896         @packages_list_statements = @new_statement_list;
2900 sub parse_package_info {
2901     my ($baseurl, $dist, $section, $session_id)= @_;
2902     my ($package);
2903     if (not defined $session_id) { $session_id = 0; }
2904     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2905     $repo_dirs{ "${repo_path}/pool" } = 1;
2907     foreach $package ("Packages.gz"){
2908         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2909         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2910         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2911     }
2912     
2916 sub get_package {
2917     my ($url, $dest, $session_id)= @_;
2918     if (not defined $session_id) { $session_id = 0; }
2920     my $tpath = dirname($dest);
2921     -d "$tpath" || mkpath "$tpath";
2923     # This is ugly, but I've no time to take a look at "how it works in perl"
2924     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2925         system("gunzip -cd '$dest' > '$dest.in'");
2926         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2927         unlink($dest);
2928         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2929     } else {
2930         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2931     }
2932     return 0;
2936 sub parse_package {
2937     my ($path, $dist, $srv_path, $session_id)= @_;
2938     if (not defined $session_id) { $session_id = 0;}
2939     my ($package, $version, $section, $description);
2940     my $PACKAGES;
2941     my $timestamp = &get_time();
2943     if(not stat("$path.in")) {
2944         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2945         return;
2946     }
2948     open($PACKAGES, "<$path.in");
2949     if(not defined($PACKAGES)) {
2950         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2951         return;
2952     }
2954     # Read lines
2955     while (<$PACKAGES>){
2956         my $line = $_;
2957         # Unify
2958         chop($line);
2960         # Use empty lines as a trigger
2961         if ($line =~ /^\s*$/){
2962             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2963             push(@packages_list_statements, $sql);
2964             $package = "none";
2965             $version = "none";
2966             $section = "none";
2967             $description = "none"; 
2968             next;
2969         }
2971         # Trigger for package name
2972         if ($line =~ /^Package:\s/){
2973             ($package)= ($line =~ /^Package: (.*)$/);
2974             next;
2975         }
2977         # Trigger for version
2978         if ($line =~ /^Version:\s/){
2979             ($version)= ($line =~ /^Version: (.*)$/);
2980             next;
2981         }
2983         # Trigger for description
2984         if ($line =~ /^Description:\s/){
2985             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2986             next;
2987         }
2989         # Trigger for section
2990         if ($line =~ /^Section:\s/){
2991             ($section)= ($line =~ /^Section: (.*)$/);
2992             next;
2993         }
2995         # Trigger for filename
2996         if ($line =~ /^Filename:\s/){
2997             my ($filename) = ($line =~ /^Filename: (.*)$/);
2998             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2999             next;
3000         }
3001     }
3003     close( $PACKAGES );
3004     unlink( "$path.in" );
3008 sub store_fileinfo {
3009     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3011     my %fileinfo = (
3012         'package' => $package,
3013         'dist' => $dist,
3014         'version' => $vers,
3015     );
3017     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3021 sub cleanup_and_extract {
3022         my $fileinfo = $repo_files{ $File::Find::name };
3024         if( defined $fileinfo ) {
3025                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3026                 my $sql;
3027                 my $package = $fileinfo->{ 'package' };
3028                 my $newver = $fileinfo->{ 'version' };
3030                 mkpath($dir);
3031                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3033                 if( -f "$dir/DEBIAN/templates" ) {
3035                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3037                         my $tmpl= ""; {
3038                                 local $/=undef;
3039                                 open FILE, "$dir/DEBIAN/templates";
3040                                 $tmpl = &encode_base64(<FILE>);
3041                                 close FILE;
3042                         }
3043                         rmtree("$dir/DEBIAN/templates");
3045                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3046                         push @packages_list_statements, $sql;
3047                 }
3048         }
3050         return;
3054 sub register_at_foreign_servers {   
3055     my ($kernel) = $_[KERNEL];
3057     # hole alle bekannten server aus known_server_db
3058     my $server_sql = "SELECT * FROM $known_server_tn";
3059     my $server_res = $known_server_db->exec_statement($server_sql);
3061     # no entries in known_server_db
3062     if (not ref(@$server_res[0]) eq "ARRAY") { 
3063         # TODO
3064     }
3066     # detect already connected clients
3067     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3068     my $client_res = $known_clients_db->exec_statement($client_sql);
3070     # send my server details to all other gosa-si-server within the network
3071     foreach my $hit (@$server_res) {
3072         my $hostname = @$hit[0];
3073         my $hostkey = &create_passwd;
3075         # add already connected clients to registration message 
3076         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3077         &add_content2xml_hash($myhash, 'key', $hostkey);
3078         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3080         # add locally loaded gosa-si modules to registration message
3081         my $loaded_modules = {};
3082         while (my ($package, $pck_info) = each %$known_modules) {
3083                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3084                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3085                                                         $loaded_modules->{$act_module} = ""; 
3086                                                 }
3087         }
3089         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3091         # add macaddress to registration message
3092         my ($host_ip, $host_port) = split(/:/, $hostname);
3093         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3094         my $network_interface= &get_interface_for_ip($local_ip);
3095         my $host_mac = &get_mac_for_interface($network_interface);
3096         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3097         
3098         # build registration message and send it
3099         my $foreign_server_msg = &create_xml_string($myhash);
3100         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3101     }
3102     
3103     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3104     return;
3108 #==== MAIN = main ==============================================================
3109 #  parse commandline options
3110 Getopt::Long::Configure( "bundling" );
3111 GetOptions("h|help" => \&usage,
3112         "c|config=s" => \$cfg_file,
3113         "f|foreground" => \$foreground,
3114         "v|verbose+" => \$verbose,
3115         "no-arp+" => \$no_arp,
3116            );
3118 #  read and set config parameters
3119 &check_cmdline_param ;
3120 &read_configfile($cfg_file, %cfg_defaults);
3121 &check_pid;
3123 $SIG{CHLD} = 'IGNORE';
3125 # forward error messages to logfile
3126 if( ! $foreground ) {
3127   open( STDIN,  '+>/dev/null' );
3128   open( STDOUT, '+>&STDIN'    );
3129   open( STDERR, '+>&STDIN'    );
3132 # Just fork, if we are not in foreground mode
3133 if( ! $foreground ) { 
3134     chdir '/'                 or die "Can't chdir to /: $!";
3135     $pid = fork;
3136     setsid                    or die "Can't start a new session: $!";
3137     umask 0;
3138 } else { 
3139     $pid = $$; 
3142 # Do something useful - put our PID into the pid_file
3143 if( 0 != $pid ) {
3144     open( LOCK_FILE, ">$pid_file" );
3145     print LOCK_FILE "$pid\n";
3146     close( LOCK_FILE );
3147     if( !$foreground ) { 
3148         exit( 0 ) 
3149     };
3152 # parse head url and revision from svn
3153 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3154 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3155 $server_headURL = defined $1 ? $1 : 'unknown' ;
3156 $server_revision = defined $2 ? $2 : 'unknown' ;
3157 if ($server_headURL =~ /\/tag\// || 
3158         $server_headURL =~ /\/branches\// ) {
3159     $server_status = "stable"; 
3160 } else {
3161     $server_status = "developmental" ;
3164 # Prepare log file
3165 $root_uid = getpwnam('root');
3166 $adm_gid = getgrnam('adm');
3167 chmod(0640, $log_file);
3168 chown($root_uid, $adm_gid, $log_file);
3169 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3171 daemon_log(" ", 1);
3172 daemon_log("$0 started!", 1);
3173 daemon_log("status: $server_status", 1);
3174 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3177     no strict "refs";
3179     if ($db_module eq "DBmysql") {
3180         # connect to incoming_db
3181         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3183         # connect to gosa-si job queue
3184         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3186         # connect to known_clients_db
3187         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3189         # connect to foreign_clients_db
3190         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3192         # connect to known_server_db
3193         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3195         # connect to login_usr_db
3196         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3198         # connect to fai_server_db 
3199         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3201         # connect to fai_release_db
3202         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3204         # connect to packages_list_db
3205         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3207         # connect to messaging_db
3208         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3210     } elsif ($db_module eq "DBsqlite") {
3211         # connect to incoming_db
3212         unlink($incoming_file_name);
3213         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3214         
3215         # connect to gosa-si job queue
3216         unlink($job_queue_file_name);  ## just for debugging
3217         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3218         chmod(0660, $job_queue_file_name);
3219         chown($root_uid, $adm_gid, $job_queue_file_name);
3220         
3221         # connect to known_clients_db
3222         unlink($known_clients_file_name);   ## just for debugging
3223         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3224         chmod(0660, $known_clients_file_name);
3225         chown($root_uid, $adm_gid, $known_clients_file_name);
3226         
3227         # connect to foreign_clients_db
3228         unlink($foreign_clients_file_name);
3229         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3230         chmod(0660, $foreign_clients_file_name);
3231         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3232         
3233         # connect to known_server_db
3234         unlink($known_server_file_name);
3235         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3236         chmod(0660, $known_server_file_name);
3237         chown($root_uid, $adm_gid, $known_server_file_name);
3238         
3239         # connect to login_usr_db
3240         unlink($login_users_file_name);
3241         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3242         chmod(0660, $login_users_file_name);
3243         chown($root_uid, $adm_gid, $login_users_file_name);
3244         
3245         # connect to fai_server_db
3246         unlink($fai_server_file_name);
3247         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3248         chmod(0660, $fai_server_file_name);
3249         chown($root_uid, $adm_gid, $fai_server_file_name);
3250         
3251         # connect to fai_release_db
3252         unlink($fai_release_file_name);
3253         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3254         chmod(0660, $fai_release_file_name);
3255         chown($root_uid, $adm_gid, $fai_release_file_name);
3256         
3257         # connect to packages_list_db
3258         #unlink($packages_list_file_name);
3259         unlink($packages_list_under_construction);
3260         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3261         chmod(0660, $packages_list_file_name);
3262         chown($root_uid, $adm_gid, $packages_list_file_name);
3263         
3264         # connect to messaging_db
3265         unlink($messaging_file_name);
3266         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3267         chmod(0660, $messaging_file_name);
3268         chown($root_uid, $adm_gid, $messaging_file_name);
3269     }
3272 # Creating tables
3273 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3274 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3275 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3276 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3277 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3278 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3279 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3280 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3281 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3282 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3285 # create xml object used for en/decrypting
3286 $xml = new XML::Simple();
3289 # foreign servers 
3290 my @foreign_server_list;
3292 # add foreign server from cfg file
3293 if ($foreign_server_string ne "") {
3294     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3295     foreach my $foreign_server (@cfg_foreign_server_list) {
3296         push(@foreign_server_list, $foreign_server);
3297     }
3299     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3302 # Perform a DNS lookup for server registration if flag is true
3303 if ($dns_lookup eq "true") {
3304     # Add foreign server from dns
3305     my @tmp_servers;
3306     if (not $server_domain) {
3307         # Try our DNS Searchlist
3308         for my $domain(get_dns_domains()) {
3309             chomp($domain);
3310             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3311             if(@$tmp_domains) {
3312                 for my $tmp_server(@$tmp_domains) {
3313                     push @tmp_servers, $tmp_server;
3314                 }
3315             }
3316         }
3317         if(@tmp_servers && length(@tmp_servers)==0) {
3318             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3319         }
3320     } else {
3321         @tmp_servers = &get_server_addresses($server_domain);
3322         if( 0 == @tmp_servers ) {
3323             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3324         }
3325     }
3327     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3329     foreach my $server (@tmp_servers) { 
3330         unshift(@foreign_server_list, $server); 
3331     }
3332 } else {
3333     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3337 # eliminate duplicate entries
3338 @foreign_server_list = &del_doubles(@foreign_server_list);
3339 my $all_foreign_server = join(", ", @foreign_server_list);
3340 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3342 # add all found foreign servers to known_server
3343 my $act_timestamp = &get_time();
3344 foreach my $foreign_server (@foreign_server_list) {
3346         # do not add myself to known_server_db
3347         if (&is_local($foreign_server)) { next; }
3348         ######################################
3350     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3351             primkey=>['hostname'],
3352             hostname=>$foreign_server,
3353             macaddress=>"",
3354             status=>'not_jet_registered',
3355             hostkey=>"none",
3356             loaded_modules => "none", 
3357             timestamp=>$act_timestamp,
3358             } );
3362 # Import all modules
3363 &import_modules;
3365 # Check wether all modules are gosa-si valid passwd check
3366 &password_check;
3368 # Prepare for using Opsi 
3369 if ($opsi_enabled eq "true") {
3370     use JSON::RPC::Client;
3371     use XML::Quote qw(:all);
3372     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3373     $opsi_client = new JSON::RPC::Client;
3377 POE::Component::Server::TCP->new(
3378         Alias => "TCP_SERVER",
3379         Port => $server_port,
3380         ClientInput => sub {
3381                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3382         my $session_id = $session->ID;
3383         my $remote_ip = $heap->{'remote_ip'};
3384                 push(@msgs_to_decrypt, $input);
3385         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3386                 $kernel->yield("msg_to_decrypt");
3387         },
3388         InlineStates => {
3389                 msg_to_decrypt => \&msg_to_decrypt,
3390                 next_task => \&next_task,
3391                 task_result => \&handle_task_result,
3392                 task_done   => \&handle_task_done,
3393                 task_debug  => \&handle_task_debug,
3394                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3395         }
3396 );
3398 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3400 # create session for repeatedly checking the job queue for jobs
3401 POE::Session->create(
3402         inline_states => {
3403                 _start => \&session_start,
3404         register_at_foreign_servers => \&register_at_foreign_servers,
3405         sig_handler => \&sig_handler,
3406         next_task => \&next_task,
3407         task_result => \&handle_task_result,
3408         task_done   => \&handle_task_done,
3409         task_debug  => \&handle_task_debug,
3410         watch_for_next_tasks => \&watch_for_next_tasks,
3411         watch_for_new_messages => \&watch_for_new_messages,
3412         watch_for_delivery_messages => \&watch_for_delivery_messages,
3413         watch_for_done_messages => \&watch_for_done_messages,
3414                 watch_for_new_jobs => \&watch_for_new_jobs,
3415         watch_for_modified_jobs => \&watch_for_modified_jobs,
3416         watch_for_done_jobs => \&watch_for_done_jobs,
3417         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3418         watch_for_old_known_clients => \&watch_for_old_known_clients,
3419         create_packages_list_db => \&run_create_packages_list_db,
3420         create_fai_server_db => \&run_create_fai_server_db,
3421         create_fai_release_db => \&run_create_fai_release_db,
3422                 recreate_packages_db => \&run_recreate_packages_db,
3423         session_run_result => \&session_run_result,
3424         session_run_debug => \&session_run_debug,
3425         session_run_done => \&session_run_done,
3426         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3427         }
3428 );
3431 POE::Kernel->run();
3432 exit;