Code

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