Code

e8abf699299c94ff63895383fd1b8d98302903c0
[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                 } else {
460                         my $info = eval($mod_name.'::get_module_info()');
461                         # Only load module if get_module_info() returns a non-null object
462                         if( $info ) {
463                                 my ($input_address, $input_key, $event_hash) = @{$info};
464                                 $known_modules->{$mod_name} = $info;
465                                 daemon_log("0 INFO: module $mod_name loaded", 5);
466                         }
467                 }
468     }   
470     close (DIR);
473 #===  FUNCTION  ================================================================
474 #         NAME:  password_check
475 #   PARAMETERS:  nothing
476 #      RETURNS:  nothing
477 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
478 #                the same password
479 #===============================================================================
480 sub password_check {
481     my $passwd_hash = {};
482     while (my ($mod_name, $mod_info) = each %$known_modules) {
483         my $mod_passwd = @$mod_info[1];
484         if (not defined $mod_passwd) { next; }
485         if (not exists $passwd_hash->{$mod_passwd}) {
486             $passwd_hash->{$mod_passwd} = $mod_name;
488         # escalates critical error
489         } else {
490             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
491             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
492             exit( -1 );
493         }
494     }
499 #===  FUNCTION  ================================================================
500 #         NAME:  sig_int_handler
501 #   PARAMETERS:  signal - string - signal arose from system
502 #      RETURNS:  nothing
503 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
504 #===============================================================================
505 sub sig_int_handler {
506     my ($signal) = @_;
508 #       if (defined($ldap_handle)) {
509 #               $ldap_handle->disconnect;
510 #       }
511     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
512     
514     daemon_log("shutting down gosa-si-server", 1);
515     system("kill `ps -C gosa-si-server -o pid=`");
517 $SIG{INT} = \&sig_int_handler;
520 sub check_key_and_xml_validity {
521     my ($crypted_msg, $module_key, $session_id) = @_;
522     my $msg;
523     my $msg_hash;
524     my $error_string;
525     eval{
526         $msg = &decrypt_msg($crypted_msg, $module_key);
528         if ($msg =~ /<xml>/i){
529             $msg =~ s/\s+/ /g;  # just for better daemon_log
530             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
531             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
533             ##############
534             # check header
535             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
536             my $header_l = $msg_hash->{'header'};
537             if( 1 > @{$header_l} ) { die 'empty header tag'; }
538             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
539             my $header = @{$header_l}[0];
540             if( 0 == length $header) { die 'empty string in header tag'; }
542             ##############
543             # check source
544             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
545             my $source_l = $msg_hash->{'source'};
546             if( 1 > @{$source_l} ) { die 'empty source tag'; }
547             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
548             my $source = @{$source_l}[0];
549             if( 0 == length $source) { die 'source error'; }
551             ##############
552             # check target
553             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
554             my $target_l = $msg_hash->{'target'};
555             if( 1 > @{$target_l} ) { die 'empty target tag'; }
556         }
557     };
558     if($@) {
559         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
560         $msg = undef;
561         $msg_hash = undef;
562     }
564     return ($msg, $msg_hash);
568 sub check_outgoing_xml_validity {
569     my ($msg, $session_id) = @_;
571     my $msg_hash;
572     eval{
573         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
575         ##############
576         # check header
577         my $header_l = $msg_hash->{'header'};
578         if( 1 != @{$header_l} ) {
579             die 'no or more than one headers specified';
580         }
581         my $header = @{$header_l}[0];
582         if( 0 == length $header) {
583             die 'header has length 0';
584         }
586         ##############
587         # check source
588         my $source_l = $msg_hash->{'source'};
589         if( 1 != @{$source_l} ) {
590             die 'no or more than 1 sources specified';
591         }
592         my $source = @{$source_l}[0];
593         if( 0 == length $source) {
594             die 'source has length 0';
595         }
596         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
597                 $source =~ /^GOSA$/i ) {
598             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
599         }
600         
601         ##############
602         # check target  
603         my $target_l = $msg_hash->{'target'};
604         if( 0 == @{$target_l} ) {
605             die "no targets specified";
606         }
607         foreach my $target (@$target_l) {
608             if( 0 == length $target) {
609                 die "target has length 0";
610             }
611             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
612                     $target =~ /^GOSA$/i ||
613                     $target =~ /^\*$/ ||
614                     $target =~ /KNOWN_SERVER/i ||
615                     $target =~ /JOBDB/i ||
616                     $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 ){
617                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
618             }
619         }
620     };
621     if($@) {
622         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
623         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
624         $msg_hash = undef;
625     }
627     return ($msg_hash);
631 sub input_from_known_server {
632     my ($input, $remote_ip, $session_id) = @_ ;  
633     my ($msg, $msg_hash, $module);
635     my $sql_statement= "SELECT * FROM known_server";
636     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
638     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
639         my $host_name = $hit->{hostname};
640         if( not $host_name =~ "^$remote_ip") {
641             next;
642         }
643         my $host_key = $hit->{hostkey};
644         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
645         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
647         # check if module can open msg envelope with module key
648         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
649         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
650             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
651             daemon_log("$@", 8);
652             next;
653         }
654         else {
655             $msg = $tmp_msg;
656             $msg_hash = $tmp_msg_hash;
657             $module = "ServerPackages";
658             last;
659         }
660     }
662     if( (!$msg) || (!$msg_hash) || (!$module) ) {
663         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
664     }
665   
666     return ($msg, $msg_hash, $module);
670 sub input_from_known_client {
671     my ($input, $remote_ip, $session_id) = @_ ;  
672     my ($msg, $msg_hash, $module);
674     my $sql_statement= "SELECT * FROM known_clients";
675     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
676     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
677         my $host_name = $hit->{hostname};
678         if( not $host_name =~ /^$remote_ip:\d*$/) {
679                 next;
680                 }
681         my $host_key = $hit->{hostkey};
682         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
683         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
685         # check if module can open msg envelope with module key
686         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
688         if( (!$msg) || (!$msg_hash) ) {
689             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
690             &daemon_log("$@", 8);
691             next;
692         }
693         else {
694             $module = "ClientPackages";
695             last;
696         }
697     }
699     if( (!$msg) || (!$msg_hash) || (!$module) ) {
700         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
701     }
703     return ($msg, $msg_hash, $module);
707 sub input_from_unknown_host {
708         no strict "refs";
709         my ($input, $session_id) = @_ ;
710         my ($msg, $msg_hash, $module);
711         my $error_string;
713         my %act_modules = %$known_modules;
715         while( my ($mod, $info) = each(%act_modules)) {
717                 # check a key exists for this module
718                 my $module_key = ${$mod."_key"};
719                 if( not defined $module_key ) {
720                         if( $mod eq 'ArpHandler' ) {
721                                 next;
722                         }
723                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
724                         next;
725                 }
726                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
728                 # check if module can open msg envelope with module key
729                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
730                 if( (not defined $msg) || (not defined $msg_hash) ) {
731                         next;
732                 } else {
733                         $module = $mod;
734                         last;
735                 }
736         }
738         if( (!$msg) || (!$msg_hash) || (!$module)) {
739                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
740         }
742         return ($msg, $msg_hash, $module);
746 sub create_ciphering {
747     my ($passwd) = @_;
748         if((!defined($passwd)) || length($passwd)==0) {
749                 $passwd = "";
750         }
751     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
752     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
753     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
754     $my_cipher->set_iv($iv);
755     return $my_cipher;
759 sub encrypt_msg {
760     my ($msg, $key) = @_;
761     my $my_cipher = &create_ciphering($key);
762     my $len;
763     {
764             use bytes;
765             $len= 16-length($msg)%16;
766     }
767     $msg = "\0"x($len).$msg;
768     $msg = $my_cipher->encrypt($msg);
769     chomp($msg = &encode_base64($msg));
770     # there are no newlines allowed inside msg
771     $msg=~ s/\n//g;
772     return $msg;
776 sub decrypt_msg {
778     my ($msg, $key) = @_ ;
779     $msg = &decode_base64($msg);
780     my $my_cipher = &create_ciphering($key);
781     $msg = $my_cipher->decrypt($msg); 
782     $msg =~ s/\0*//g;
783     return $msg;
787 sub get_encrypt_key {
788     my ($target) = @_ ;
789     my $encrypt_key;
790     my $error = 0;
792     # target can be in known_server
793     if( not defined $encrypt_key ) {
794         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
795         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
796         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
797             my $host_name = $hit->{hostname};
798             if( $host_name ne $target ) {
799                 next;
800             }
801             $encrypt_key = $hit->{hostkey};
802             last;
803         }
804     }
806     # target can be in known_client
807     if( not defined $encrypt_key ) {
808         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
809         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
810         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
811             my $host_name = $hit->{hostname};
812             if( $host_name ne $target ) {
813                 next;
814             }
815             $encrypt_key = $hit->{hostkey};
816             last;
817         }
818     }
820     return $encrypt_key;
824 #===  FUNCTION  ================================================================
825 #         NAME:  open_socket
826 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
827 #                [PeerPort] string necessary if port not appended by PeerAddr
828 #      RETURNS:  socket IO::Socket::INET
829 #  DESCRIPTION:  open a socket to PeerAddr
830 #===============================================================================
831 sub open_socket {
832     my ($PeerAddr, $PeerPort) = @_ ;
833     if(defined($PeerPort)){
834         $PeerAddr = $PeerAddr.":".$PeerPort;
835     }
836     my $socket;
837     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
838             Porto => "tcp",
839             Type => SOCK_STREAM,
840             Timeout => 5,
841             );
842     if(not defined $socket) {
843         return;
844     }
845 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
846     return $socket;
850 #sub get_local_ip_for_remote_ip {
851 #       my $remote_ip= shift;
852 #       my $result="0.0.0.0";
854 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855 #               if($remote_ip eq "127.0.0.1") {
856 #                       $result = "127.0.0.1";
857 #               } else {
858 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
860 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861 #                               or die "Could not open $PROC_NET_ROUTE";
863 #                       my @ifs = <PROC_NET_ROUTE>;
865 #                       close(PROC_NET_ROUTE);
867 #                       # Eat header line
868 #                       shift @ifs;
869 #                       chomp @ifs;
870 #                       foreach my $line(@ifs) {
871 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872 #                               my $destination;
873 #                               my $mask;
874 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879 #                                       # destination matches route, save mac and exit
880 #                                       $result= &get_ip($Iface);
881 #                                       last;
882 #                               }
883 #                       }
884 #               }
885 #       } else {
886 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
887 #       }
888 #       return $result;
889 #}
892 sub send_msg_to_target {
893     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894     my $error = 0;
895     my $header;
896     my $timestamp = &get_time();
897     my $new_status;
898     my $act_status;
899     my ($sql_statement, $res);
900   
901     if( $msg_header ) {
902         $header = "'$msg_header'-";
903     } else {
904         $header = "";
905     }
907         # Patch the source ip
908         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911         }
913     # encrypt xml msg
914     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916     # opensocket
917     my $socket = &open_socket($address);
918     if( !$socket ) {
919         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
920         $error++;
921     }
922     
923     if( $error == 0 ) {
924         # send xml msg
925         print $socket $crypted_msg."\n";
927         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928         daemon_log("$session_id DEBUG: message:\n$msg", 9);
929         
930     }
932     # close socket in any case
933     if( $socket ) {
934         close $socket;
935     }
937     if( $error > 0 ) { $new_status = "down"; }
938     else { $new_status = $msg_header; }
941     # known_clients
942     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
943     $res = $known_clients_db->select_dbentry($sql_statement);
944     if( keys(%$res) == 1) {
945         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
946         if ($act_status eq "down" && $new_status eq "down") {
947             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948             $res = $known_clients_db->del_dbentry($sql_statement);
949             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950         } else { 
951             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952             $res = $known_clients_db->update_dbentry($sql_statement);
953             if($new_status eq "down"){
954                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955             } else {
956                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957             }
958         }
959     }
961     # known_server
962     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963     $res = $known_server_db->select_dbentry($sql_statement);
964     if( keys(%$res) == 1) {
965         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
966         if ($act_status eq "down" && $new_status eq "down") {
967             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968             $res = $known_server_db->del_dbentry($sql_statement);
969             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970         } 
971         else { 
972             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973             $res = $known_server_db->update_dbentry($sql_statement);
974             if($new_status eq "down"){
975                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976             } else {
977                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978             }
979         }
980     }
981     return $error; 
985 sub update_jobdb_status_for_send_msgs {
986     my ($answer, $error) = @_;
987     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
988         my $jobdb_id = $1;
989             
990         # sending msg faild
991         if( $error ) {
992             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
993                 my $sql_statement = "UPDATE $job_queue_tn ".
994                     "SET status='error', result='can not deliver msg, please consult log file' ".
995                     "WHERE id=$jobdb_id";
996                 my $res = $job_db->update_dbentry($sql_statement);
997             }
999         # sending msg was successful
1000         } else {
1001             my $sql_statement = "UPDATE $job_queue_tn ".
1002                 "SET status='done' ".
1003                 "WHERE id=$jobdb_id AND status='processed'";
1004             my $res = $job_db->update_dbentry($sql_statement);
1005         }
1006     }
1010 sub sig_handler {
1011         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1012         daemon_log("0 INFO got signal '$signal'", 1); 
1013         $kernel->sig_handled();
1014         return;
1018 sub msg_to_decrypt {
1019         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1020         my $session_id = $session->ID;
1021         my ($msg, $msg_hash, $module);
1022         my $error = 0;
1024         # hole neue msg aus @msgs_to_decrypt
1025         my $next_msg = shift @msgs_to_decrypt;
1027         # entschlüssle sie
1029         # msg is from a new client or gosa
1030         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1031         # msg is from a gosa-si-server
1032         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1034         }
1035         # msg is from a gosa-si-client
1036         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1038         }
1039         # an error occurred
1040         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1042                 # could not understand a msg from its server the client cause a re-registering process
1043                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1044                         "' to cause a re-registering of the client if necessary", 3);
1045                 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046                 my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1047                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1048                         my $host_name = $hit->{'hostname'};
1049                         my $host_key = $hit->{'hostkey'};
1050                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051                         my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052                         &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053                 }
1054                 $error++;
1055         }
1058         my $header;
1059         my $target;
1060         my $source;
1061         my $done = 0;
1062         my $sql;
1063         my $res;
1065         # check whether this message should be processed here
1066         if ($error == 0) {
1067                 $header = @{$msg_hash->{'header'}}[0];
1068                 $target = @{$msg_hash->{'target'}}[0];
1069                 $source = @{$msg_hash->{'source'}}[0];
1070                 my $not_found_in_known_clients_db = 0;
1071                 my $not_found_in_known_server_db = 0;
1072                 my $not_found_in_foreign_clients_db = 0;
1073                 my $local_address;
1074                 my $local_mac;
1075                 my ($target_ip, $target_port) = split(':', $target);
1077                 # Determine the local ip address if target is an ip address
1078                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1079                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1080                 } else {
1081                         $local_address = $server_address;
1082                 }
1084                 # Determine the local mac address if target is a mac address
1085                 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) {
1086                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1087                         my $network_interface= &get_interface_for_ip($loc_ip);
1088                         $local_mac = &get_mac_for_interface($network_interface);
1089                 } else {
1090                         $local_mac = $server_mac_address;
1091                 }
1093                 # target and source is equal to GOSA -> process here
1094                 if (not $done) {
1095                         if ($target eq "GOSA" && $source eq "GOSA") {
1096                                 $done = 1;                    
1097                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1098                         }
1099                 }
1101                 # target is own address without forward_to_gosa-tag -> process here
1102                 if (not $done) {
1103                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1104                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1105                                 $done = 1;
1106                                 if ($source eq "GOSA") {
1107                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1108                                 }
1109                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1110                         }
1111                 }
1113                 # target is a client address in known_clients -> process here
1114                 if (not $done) {
1115                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1116                         $res = $known_clients_db->select_dbentry($sql);
1117                         if (keys(%$res) > 0) {
1118                                 $done = 1; 
1119                                 my $hostname = $res->{1}->{'hostname'};
1120                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1121                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1122                                 if ($source eq "GOSA") {
1123                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1124                                 }
1125                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1127                         } else {
1128                                 $not_found_in_known_clients_db = 1;
1129                         }
1130                 }
1132                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1133                 if (not $done) {
1134                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1135                         my $gosa_at;
1136                         my $gosa_session_id;
1137                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1138                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1139                                 if ($gosa_at ne $local_address) {
1140                                         $done = 1;
1141                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1142                                 }
1143                         }
1144                 }
1146                 # if message should be processed here -> add message to incoming_db
1147                 if ($done) {
1148                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1149                         # so gosa-si-server knows how to process this kind of messages
1150                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1151                                 $module = "GosaPackages";
1152                         }
1154                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1155                                         primkey=>[],
1156                                         headertag=>$header,
1157                                         targettag=>$target,
1158                                         xmlmessage=>&encode_base64($msg),
1159                                         timestamp=>&get_time,
1160                                         module=>$module,
1161                                         sessionid=>$session_id,
1162                                 } );
1164                 }
1166                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1167                 if (not $done) {
1168                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1169                         my $gosa_at;
1170                         my $gosa_session_id;
1171                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1172                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1173                                 if ($gosa_at eq $local_address) {
1174                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1175                                         if( defined $session_reference ) {
1176                                                 $heap = $session_reference->get_heap();
1177                                         }
1178                                         if(exists $heap->{'client'}) {
1179                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1180                                                 $heap->{'client'}->put($msg);
1181                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1182                                         }
1183                                         $done = 1;
1184                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1185                                 }
1186                         }
1188                 }
1190                 # target is a client address in foreign_clients -> forward to registration server
1191                 if (not $done) {
1192                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1193                         $res = $foreign_clients_db->select_dbentry($sql);
1194                         if (keys(%$res) > 0) {
1195                                 my $hostname = $res->{1}->{'hostname'};
1196                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1197                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1198                                 my $regserver = $res->{1}->{'regserver'};
1199                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1200                                 my $res = $known_server_db->select_dbentry($sql);
1201                                 if (keys(%$res) > 0) {
1202                                         my $regserver_key = $res->{1}->{'hostkey'};
1203                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1204                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1205                                         if ($source eq "GOSA") {
1206                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1207                                         }
1208                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1209                                 }
1210                                 $done = 1;
1211                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1212                         } else {
1213                                 $not_found_in_foreign_clients_db = 1;
1214                         }
1215                 }
1217                 # target is a server address -> forward to server
1218                 if (not $done) {
1219                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1220                         $res = $known_server_db->select_dbentry($sql);
1221                         if (keys(%$res) > 0) {
1222                                 my $hostkey = $res->{1}->{'hostkey'};
1224                                 if ($source eq "GOSA") {
1225                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1226                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1228                                 }
1230                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1231                                 $done = 1;
1232                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1233                         } else {
1234                                 $not_found_in_known_server_db = 1;
1235                         }
1236                 }
1239                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1240                 if ( $not_found_in_foreign_clients_db 
1241                         && $not_found_in_known_server_db
1242                         && $not_found_in_known_clients_db) {
1243                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1244                                         primkey=>[],
1245                                         headertag=>$header,
1246                                         targettag=>$target,
1247                                         xmlmessage=>&encode_base64($msg),
1248                                         timestamp=>&get_time,
1249                                         module=>$module,
1250                                         sessionid=>$session_id,
1251                                 } );
1252                         $done = 1;
1253                         &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1254                 }
1257                 if (not $done) {
1258                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1259                         if ($source eq "GOSA") {
1260                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1261                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1263                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1264                                 if( defined $session_reference ) {
1265                                         $heap = $session_reference->get_heap();
1266                                 }
1267                                 if(exists $heap->{'client'}) {
1268                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1269                                         $heap->{'client'}->put($error_msg);
1270                                 }
1271                         }
1272                 }
1274         }
1276         return;
1280 sub next_task {
1281     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1282     my $running_task = POE::Wheel::Run->new(
1283             Program => sub { process_task($session, $heap, $task) },
1284             StdioFilter => POE::Filter::Reference->new(),
1285             StdoutEvent  => "task_result",
1286             StderrEvent  => "task_debug",
1287             CloseEvent   => "task_done",
1288             );
1289     $heap->{task}->{ $running_task->ID } = $running_task;
1292 sub handle_task_result {
1293     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1294     my $client_answer = $result->{'answer'};
1295     if( $client_answer =~ s/session_id=(\d+)$// ) {
1296         my $session_id = $1;
1297         if( defined $session_id ) {
1298             my $session_reference = $kernel->ID_id_to_session($session_id);
1299             if( defined $session_reference ) {
1300                 $heap = $session_reference->get_heap();
1301             }
1302         }
1304         if(exists $heap->{'client'}) {
1305             $heap->{'client'}->put($client_answer);
1306         }
1307     }
1308     $kernel->sig(CHLD => "child_reap");
1311 sub handle_task_debug {
1312     my $result = $_[ARG0];
1313     print STDERR "$result\n";
1316 sub handle_task_done {
1317     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1318     delete $heap->{task}->{$task_id};
1321 sub process_task {
1322     no strict "refs";
1323     #CHECK: Not @_[...]?
1324     my ($session, $heap, $task) = @_;
1325     my $error = 0;
1326     my $answer_l;
1327     my ($answer_header, @answer_target_l, $answer_source);
1328     my $client_answer = "";
1330     # prepare all variables needed to process message
1331     #my $msg = $task->{'xmlmessage'};
1332     my $msg = &decode_base64($task->{'xmlmessage'});
1333     my $incoming_id = $task->{'id'};
1334     my $module = $task->{'module'};
1335     my $header =  $task->{'headertag'};
1336     my $session_id = $task->{'sessionid'};
1337                 my $msg_hash;
1338                 eval {
1339         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1340                 }; 
1341                 daemon_log("ERROR: XML failure '$@'") if ($@);
1342     my $source = @{$msg_hash->{'source'}}[0];
1343     
1344     # set timestamp of incoming client uptodate, so client will not 
1345     # be deleted from known_clients because of expiration
1346     my $act_time = &get_time();
1347     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1348     my $res = $known_clients_db->exec_statement($sql);
1350     ######################
1351     # process incoming msg
1352     if( $error == 0) {
1353         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1354         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1355         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1357         if ( 0 < @{$answer_l} ) {
1358             my $answer_str = join("\n", @{$answer_l});
1359             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1360                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1361             }
1362             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1363         } else {
1364             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1365         }
1367     }
1368     if( !$answer_l ) { $error++ };
1370     ########
1371     # answer
1372     if( $error == 0 ) {
1374         foreach my $answer ( @{$answer_l} ) {
1375             # check outgoing msg to xml validity
1376             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1377             if( not defined $answer_hash ) { next; }
1378             
1379             $answer_header = @{$answer_hash->{'header'}}[0];
1380             @answer_target_l = @{$answer_hash->{'target'}};
1381             $answer_source = @{$answer_hash->{'source'}}[0];
1383             # deliver msg to all targets 
1384             foreach my $answer_target ( @answer_target_l ) {
1386                 # targets of msg are all gosa-si-clients in known_clients_db
1387                 if( $answer_target eq "*" ) {
1388                     # answer is for all clients
1389                     my $sql_statement= "SELECT * FROM known_clients";
1390                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1391                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1392                         my $host_name = $hit->{hostname};
1393                         my $host_key = $hit->{hostkey};
1394                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1395                         &update_jobdb_status_for_send_msgs($answer, $error);
1396                     }
1397                 }
1399                 # targets of msg are all gosa-si-server in known_server_db
1400                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1401                     # answer is for all server in known_server
1402                     my $sql_statement= "SELECT * FROM $known_server_tn";
1403                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1404                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1405                         my $host_name = $hit->{hostname};
1406                         my $host_key = $hit->{hostkey};
1407                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1408                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1409                         &update_jobdb_status_for_send_msgs($answer, $error);
1410                     }
1411                 }
1413                 # target of msg is GOsa
1414                                 elsif( $answer_target eq "GOSA" ) {
1415                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1416                                         my $add_on = "";
1417                     if( defined $session_id ) {
1418                         $add_on = ".session_id=$session_id";
1419                     }
1420                     # answer is for GOSA and has to returned to connected client
1421                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1422                     $client_answer = $gosa_answer.$add_on;
1423                 }
1425                 # target of msg is job queue at this host
1426                 elsif( $answer_target eq "JOBDB") {
1427                     $answer =~ /<header>(\S+)<\/header>/;   
1428                     my $header;
1429                     if( defined $1 ) { $header = $1; }
1430                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1431                     &update_jobdb_status_for_send_msgs($answer, $error);
1432                 }
1434                 # Target of msg is a mac address
1435                 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 ) {
1436                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1437                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1438                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1439                     my $found_ip_flag = 0;
1440                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1441                         my $host_name = $hit->{hostname};
1442                         my $host_key = $hit->{hostkey};
1443                         $answer =~ s/$answer_target/$host_name/g;
1444                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1445                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1446                         &update_jobdb_status_for_send_msgs($answer, $error);
1447                         $found_ip_flag++ ;
1448                     }   
1449                     if ($found_ip_flag == 0) {
1450                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1451                         my $res = $foreign_clients_db->select_dbentry($sql);
1452                         while( my ($hit_num, $hit) = each %{ $res } ) {
1453                             my $host_name = $hit->{hostname};
1454                             my $reg_server = $hit->{regserver};
1455                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1456                             
1457                             # Fetch key for reg_server
1458                             my $reg_server_key;
1459                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1460                             my $res = $known_server_db->select_dbentry($sql);
1461                             if (exists $res->{1}) {
1462                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1463                             } else {
1464                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1465                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1466                                 $reg_server_key = undef;
1467                             }
1469                             # Send answer to server where client is registered
1470                             if (defined $reg_server_key) {
1471                                 $answer =~ s/$answer_target/$host_name/g;
1472                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1473                                 &update_jobdb_status_for_send_msgs($answer, $error);
1474                                 $found_ip_flag++ ;
1475                             }
1476                         }
1477                     }
1478                     if( $found_ip_flag == 0) {
1479                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1480                     }
1482                 # Answer is for one specific host   
1483                 } else {
1484                     # get encrypt_key
1485                     my $encrypt_key = &get_encrypt_key($answer_target);
1486                     if( not defined $encrypt_key ) {
1487                         # unknown target
1488                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1489                         next;
1490                     }
1491                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1492                     &update_jobdb_status_for_send_msgs($answer, $error);
1493                 }
1494             }
1495         }
1496     }
1498     my $filter = POE::Filter::Reference->new();
1499     my %result = ( 
1500             status => "seems ok to me",
1501             answer => $client_answer,
1502             );
1504     my $output = $filter->put( [ \%result ] );
1505     print @$output;
1510 sub session_start {
1511     my ($kernel) = $_[KERNEL];
1512     $global_kernel = $kernel;
1513     $kernel->yield('register_at_foreign_servers');
1514         $kernel->yield('create_fai_server_db', $fai_server_tn );
1515         $kernel->yield('create_fai_release_db', $fai_release_tn );
1516     $kernel->yield('watch_for_next_tasks');
1517         $kernel->sig(USR1 => "sig_handler");
1518         $kernel->sig(USR2 => "recreate_packages_db");
1519         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1520         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1521     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1522         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1523     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1524         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1525     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1527     # Start opsi check
1528     if ($opsi_enabled eq "true") {
1529         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1530     }
1535 sub watch_for_done_jobs {
1536     #CHECK: $heap for what?
1537     my ($kernel,$heap) = @_[KERNEL, HEAP];
1539     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1540         my $res = $job_db->select_dbentry( $sql_statement );
1542     while( my ($id, $hit) = each %{$res} ) {
1543         my $jobdb_id = $hit->{id};
1544         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1545         my $res = $job_db->del_dbentry($sql_statement); 
1546     }
1548     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1552 sub watch_for_opsi_jobs {
1553     my ($kernel) = $_[KERNEL];
1555     # 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 
1556     # opsi install job is to parse the xml message. There is still the correct header.
1557     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1558         my $res = $job_db->select_dbentry( $sql_statement );
1560     # Ask OPSI for an update of the running jobs
1561     while (my ($id, $hit) = each %$res ) {
1562         # Determine current parameters of the job
1563         my $hostId = $hit->{'plainname'};
1564         my $macaddress = $hit->{'macaddress'};
1565         my $progress = $hit->{'progress'};
1567         my $result= {};
1568         
1569         # For hosts, only return the products that are or get installed
1570         my $callobj;
1571         $callobj = {
1572             method  => 'getProductStates_hash',
1573             params  => [ $hostId ],
1574             id  => 1,
1575         };
1576         
1577         my $hres = $opsi_client->call($opsi_url, $callobj);
1578         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1579         if (not &check_opsi_res($hres)) {
1580             my $htmp= $hres->result->{$hostId};
1581         
1582             # Check state != not_installed or action == setup -> load and add
1583             my $products= 0;
1584             my $installed= 0;
1585             my $installing = 0;
1586             my $error= 0;  
1587             my @installed_list;
1588             my @error_list;
1589             my $act_status = "none";
1590             foreach my $product (@{$htmp}){
1592                 if ($product->{'installationStatus'} ne "not_installed" or
1593                         $product->{'actionRequest'} eq "setup"){
1595                     # Increase number of products for this host
1596                     $products++;
1597         
1598                     if ($product->{'installationStatus'} eq "failed"){
1599                         $result->{$product->{'productId'}}= "error";
1600                         unshift(@error_list, $product->{'productId'});
1601                         $error++;
1602                     }
1603                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1604                         $result->{$product->{'productId'}}= "installed";
1605                         unshift(@installed_list, $product->{'productId'});
1606                         $installed++;
1607                     }
1608                     if ($product->{'installationStatus'} eq "installing"){
1609                         $result->{$product->{'productId'}}= "installing";
1610                         $installing++;
1611                         $act_status = "installing - ".$product->{'productId'};
1612                     }
1613                 }
1614             }
1615         
1616             # Estimate "rough" progress, avoid division by zero
1617             if ($products == 0) {
1618                 $result->{'progress'}= 0;
1619             } else {
1620                 $result->{'progress'}= int($installed * 100 / $products);
1621             }
1623             # Set updates in job queue
1624             if ((not $error) && (not $installing) && ($installed)) {
1625                 $act_status = "installed - ".join(", ", @installed_list);
1626             }
1627             if ($error) {
1628                 $act_status = "error - ".join(", ", @error_list);
1629             }
1630             if ($progress ne $result->{'progress'} ) {
1631                 # Updating progress and result 
1632                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1633                 my $update_res = $job_db->update_dbentry($update_statement);
1634             }
1635             if ($progress eq 100) { 
1636                 # Updateing status
1637                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1638                 if ($error) {
1639                     $done_statement .= "status='error'";
1640                 } else {
1641                     $done_statement .= "status='done'";
1642                 }
1643                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1644                 my $done_res = $job_db->update_dbentry($done_statement);
1645             }
1648         }
1649     }
1651     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1655 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1656 sub watch_for_modified_jobs {
1657     my ($kernel,$heap) = @_[KERNEL, HEAP];
1659     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1660     my $res = $job_db->select_dbentry( $sql_statement );
1661     
1662     # if db contains no jobs which should be update, do nothing
1663     if (keys %$res != 0) {
1665         if ($job_synchronization  eq "true") {
1666             # make out of the db result a gosa-si message   
1667             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1668  
1669             # update all other SI-server
1670             &inform_all_other_si_server($update_msg);
1671         }
1673         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1674         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1675         $res = $job_db->update_dbentry($sql_statement);
1676     }
1678     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1682 sub watch_for_new_jobs {
1683         if($watch_for_new_jobs_in_progress == 0) {
1684                 $watch_for_new_jobs_in_progress = 1;
1685                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1687                 # check gosa job quaeue for jobs with executable timestamp
1688                 my $timestamp = &get_time();
1689                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1690                 my $res = $job_db->exec_statement( $sql_statement );
1692                 # Merge all new jobs that would do the same actions
1693                 my @drops;
1694                 my $hits;
1695                 foreach my $hit (reverse @{$res} ) {
1696                         my $macaddress= lc @{$hit}[8];
1697                         my $headertag= @{$hit}[5];
1698                         if(
1699                                 defined($hits->{$macaddress}) &&
1700                                 defined($hits->{$macaddress}->{$headertag}) &&
1701                                 defined($hits->{$macaddress}->{$headertag}[0])
1702                         ) {
1703                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1704                         }
1705                         $hits->{$macaddress}->{$headertag}= $hit;
1706                 }
1708                 # Delete new jobs with a matching job in state 'processing'
1709                 foreach my $macaddress (keys %{$hits}) {
1710                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1711                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1712                                 if(defined($jobdb_id)) {
1713                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1714                                         my $res = $job_db->exec_statement( $sql_statement );
1715                                         foreach my $hit (@{$res}) {
1716                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1717                                         }
1718                                 } else {
1719                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1720                                 }
1721                         }
1722                 }
1724                 # Commit deletion
1725                 $job_db->exec_statementlist(\@drops);
1727                 # Look for new jobs that could be executed
1728                 foreach my $macaddress (keys %{$hits}) {
1730                         # Look if there is an executing job
1731                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1732                         my $res = $job_db->exec_statement( $sql_statement );
1734                         # Skip new jobs for host if there is a processing job
1735                         if(defined($res) and defined @{$res}[0]) {
1736                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1737                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1738                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1739                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1740                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1741                                         if(defined($res_2) and defined @{$res_2}[0]) {
1742                                                 # Set status from goto-activation to 'waiting' and update timestamp
1743                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1744                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1745                                         }
1746                                 }
1747                                 next;
1748                         }
1750                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1751                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1752                                 if(defined($jobdb_id)) {
1753                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1755                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1756                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1757                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1759                                         # expect macaddress is unique!!!!!!
1760                                         my $target = $res_hash->{1}->{hostname};
1762                                         # change header
1763                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1765                                         # add sqlite_id
1766                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1768                                         $job_msg =~ /<header>(\S+)<\/header>/;
1769                                         my $header = $1 ;
1770                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1772                                         # update status in job queue to 'processing'
1773                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1774                                         my $res = $job_db->update_dbentry($sql_statement);
1775 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1777                                         # We don't want parallel processing
1778                                         last;
1779                                 }
1780                         }
1781                 }
1783                 $watch_for_new_jobs_in_progress = 0;
1784                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1785         }
1789 sub watch_for_new_messages {
1790     my ($kernel,$heap) = @_[KERNEL, HEAP];
1791     my @coll_user_msg;   # collection list of outgoing messages
1792     
1793     # check messaging_db for new incoming messages with executable timestamp
1794     my $timestamp = &get_time();
1795     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1796     my $res = $messaging_db->exec_statement( $sql_statement );
1797         foreach my $hit (@{$res}) {
1799         # create outgoing messages
1800         my $message_to = @{$hit}[3];
1801         # translate message_to to plain login name
1802         my @message_to_l = split(/,/, $message_to);  
1803                 my %receiver_h; 
1804                 foreach my $receiver (@message_to_l) {
1805                         if ($receiver =~ /^u_([\s\S]*)$/) {
1806                                 $receiver_h{$1} = 0;
1807                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1808                                 my $group_name = $1;
1809                                 # fetch all group members from ldap and add them to receiver hash
1810                                 my $ldap_handle = &get_ldap_handle();
1811                                 if (defined $ldap_handle) {
1812                                                 my $mesg = $ldap_handle->search(
1813                                                                                 base => $ldap_base,
1814                                                                                 scope => 'sub',
1815                                                                                 attrs => ['memberUid'],
1816                                                                                 filter => "cn=$group_name",
1817                                                                                 );
1818                                                 if ($mesg->count) {
1819                                                                 my @entries = $mesg->entries;
1820                                                                 foreach my $entry (@entries) {
1821                                                                                 my @receivers= $entry->get_value("memberUid");
1822                                                                                 foreach my $receiver (@receivers) { 
1823                                                                                                 $receiver_h{$1} = 0;
1824                                                                                 }
1825                                                                 }
1826                                                 } 
1827                                                 # translating errors ?
1828                                                 if ($mesg->code) {
1829                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1830                                                 }
1831                                 # ldap handle error ?           
1832                                 } else {
1833                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1834                                 }
1835                         } else {
1836                                 my $sbjct = &encode_base64(@{$hit}[1]);
1837                                 my $msg = &encode_base64(@{$hit}[7]);
1838                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1839                         }
1840                 }
1841                 my @receiver_l = keys(%receiver_h);
1843         my $message_id = @{$hit}[0];
1845         #add each outgoing msg to messaging_db
1846         my $receiver;
1847         foreach $receiver (@receiver_l) {
1848             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1849                 "VALUES ('".
1850                 $message_id."', '".    # id
1851                 @{$hit}[1]."', '".     # subject
1852                 @{$hit}[2]."', '".     # message_from
1853                 $receiver."', '".      # message_to
1854                 "none"."', '".         # flag
1855                 "out"."', '".          # direction
1856                 @{$hit}[6]."', '".     # delivery_time
1857                 @{$hit}[7]."', '".     # message
1858                 $timestamp."'".     # timestamp
1859                 ")";
1860             &daemon_log("M DEBUG: $sql_statement", 1);
1861             my $res = $messaging_db->exec_statement($sql_statement);
1862             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1863         }
1865         # set incoming message to flag d=deliverd
1866         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1867         &daemon_log("M DEBUG: $sql_statement", 7);
1868         $res = $messaging_db->update_dbentry($sql_statement);
1869         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1870     }
1872     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1873     return;
1876 sub watch_for_delivery_messages {
1877     my ($kernel, $heap) = @_[KERNEL, HEAP];
1879     # select outgoing messages
1880     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1881     #&daemon_log("0 DEBUG: $sql", 7);
1882     my $res = $messaging_db->exec_statement( $sql_statement );
1883     
1884     # build out msg for each    usr
1885     foreach my $hit (@{$res}) {
1886         my $receiver = @{$hit}[3];
1887         my $msg_id = @{$hit}[0];
1888         my $subject = @{$hit}[1];
1889         my $message = @{$hit}[7];
1891         # resolve usr -> host where usr is logged in
1892         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1893         #&daemon_log("0 DEBUG: $sql", 7);
1894         my $res = $login_users_db->exec_statement($sql);
1896         # reciver is logged in nowhere
1897         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1899                 my $send_succeed = 0;
1900                 foreach my $hit (@$res) {
1901                                 my $receiver_host = @$hit[0];
1902                 my $delivered2host = 0;
1903                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1905                                 # Looking for host in know_clients_db 
1906                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1907                                 my $res = $known_clients_db->exec_statement($sql);
1909                 # Host is known in known_clients_db
1910                 if (ref(@$res[0]) eq "ARRAY") {
1911                     my $receiver_key = @{@{$res}[0]}[2];
1912                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1913                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1914                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1915                     if ($error == 0 ) {
1916                         $send_succeed++ ;
1917                         $delivered2host++ ;
1918                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1919                     } else {
1920                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1921                     }
1922                 }
1923                 
1924                 # Message already send, do not need to do anything more, otherwise ...
1925                 if ($delivered2host) { next;}
1926     
1927                 # ...looking for host in foreign_clients_db
1928                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1929                 $res = $foreign_clients_db->exec_statement($sql);
1930   
1931                                 # Host is known in foreign_clients_db 
1932                                 if (ref(@$res[0]) eq "ARRAY") { 
1933                     my $registration_server = @{@{$res}[0]}[2];
1934                     
1935                     # Fetch encryption key for registration server
1936                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1937                     my $res = $known_server_db->exec_statement($sql);
1938                     if (ref(@$res[0]) eq "ARRAY") { 
1939                         my $registration_server_key = @{@{$res}[0]}[3];
1940                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1941                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1942                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1943                         if ($error == 0 ) {
1944                             $send_succeed++ ;
1945                             $delivered2host++ ;
1946                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1947                         } else {
1948                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1949                         }
1951                     } else {
1952                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1953                                 "registrated at server '$registration_server', ".
1954                                 "but no data available in known_server_db ", 1); 
1955                     }
1956                 }
1957                 
1958                 if (not $delivered2host) {
1959                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1960                 }
1961                 }
1963                 if ($send_succeed) {
1964                                 # set outgoing msg at db to deliverd
1965                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1966                                 my $res = $messaging_db->exec_statement($sql); 
1967                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1968                 } else {
1969             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1970         }
1971         }
1973     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1974     return;
1978 sub watch_for_done_messages {
1979     my ($kernel,$heap) = @_[KERNEL, HEAP];
1981     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1982     #&daemon_log("0 DEBUG: $sql", 7);
1983     my $res = $messaging_db->exec_statement($sql); 
1985     foreach my $hit (@{$res}) {
1986         my $msg_id = @{$hit}[0];
1988         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1989         #&daemon_log("0 DEBUG: $sql", 7); 
1990         my $res = $messaging_db->exec_statement($sql);
1992         # not all usr msgs have been seen till now
1993         if ( ref(@$res[0]) eq "ARRAY") { next; }
1994         
1995         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1996         #&daemon_log("0 DEBUG: $sql", 7);
1997         $res = $messaging_db->exec_statement($sql);
1998     
1999     }
2001     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2002     return;
2006 sub watch_for_old_known_clients {
2007     my ($kernel,$heap) = @_[KERNEL, HEAP];
2009     my $sql_statement = "SELECT * FROM $known_clients_tn";
2010     my $res = $known_clients_db->select_dbentry( $sql_statement );
2012     my $act_time = int(&get_time());
2014     while ( my ($hit_num, $hit) = each %$res) {
2015         my $expired_timestamp = int($hit->{'timestamp'});
2016         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2017         my $dt = DateTime->new( year   => $1,
2018                 month  => $2,
2019                 day    => $3,
2020                 hour   => $4,
2021                 minute => $5,
2022                 second => $6,
2023                 );
2025         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2026         $expired_timestamp = $dt->ymd('').$dt->hms('');
2027         if ($act_time > $expired_timestamp) {
2028             my $hostname = $hit->{'hostname'};
2029             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2030             my $del_res = $known_clients_db->exec_statement($del_sql);
2032             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2033         }
2035     }
2037     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2041 sub watch_for_next_tasks {
2042     my ($kernel,$heap) = @_[KERNEL, HEAP];
2044     my $sql = "SELECT * FROM $incoming_tn";
2045     my $res = $incoming_db->select_dbentry($sql);
2047     while ( my ($hit_num, $hit) = each %$res) {
2048         my $headertag = $hit->{'headertag'};
2049         if ($headertag =~ /^answer_(\d+)/) {
2050             # do not start processing, this message is for a still running POE::Wheel
2051             next;
2052         }
2053         my $message_id = $hit->{'id'};
2054         $kernel->yield('next_task', $hit);
2056         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2057         my $res = $incoming_db->exec_statement($sql);
2058     }
2060     $kernel->delay_set('watch_for_next_tasks', 1); 
2064 sub get_ldap_handle {
2065         my ($session_id) = @_;
2066         my $heap;
2067         my $ldap_handle;
2069         if (not defined $session_id ) { $session_id = 0 };
2070         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2072         if ($session_id == 0) {
2073                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2074                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2075                 if (defined $ldap_handle) {
2076                         $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!"); 
2077                 } else {
2078                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2079                 }
2081         } else {
2082                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2083                 if( defined $session_reference ) {
2084                         $heap = $session_reference->get_heap();
2085                 }
2087                 if (not defined $heap) {
2088                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2089                         return;
2090                 }
2092                 # TODO: This "if" is nonsense, because it doesn't prove that the
2093                 #       used handle is still valid - or if we've to reconnect...
2094                 #if (not exists $heap->{ldap_handle}) {
2095                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2096                         $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!"); 
2097                         $heap->{ldap_handle} = $ldap_handle;
2098                 #}
2099         }
2100         return $ldap_handle;
2104 sub change_fai_state {
2105     my ($st, $targets, $session_id) = @_;
2106     $session_id = 0 if not defined $session_id;
2107     # Set FAI state to localboot
2108     my %mapActions= (
2109         reboot    => '',
2110         update    => 'softupdate',
2111         localboot => 'localboot',
2112         reinstall => 'install',
2113         rescan    => '',
2114         wake      => '',
2115         memcheck  => 'memcheck',
2116         sysinfo   => 'sysinfo',
2117         install   => 'install',
2118     );
2120     # Return if this is unknown
2121     if (!exists $mapActions{ $st }){
2122         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2123       return;
2124     }
2126     my $state= $mapActions{ $st };
2128     my $ldap_handle = &get_ldap_handle($session_id);
2129     if( defined($ldap_handle) ) {
2131       # Build search filter for hosts
2132         my $search= "(&(objectClass=GOhard)";
2133         foreach (@{$targets}){
2134             $search.= "(macAddress=$_)";
2135         }
2136         $search.= ")";
2138       # If there's any host inside of the search string, procress them
2139         if (!($search =~ /macAddress/)){
2140             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2141             return;
2142         }
2144       # Perform search for Unit Tag
2145       my $mesg = $ldap_handle->search(
2146           base   => $ldap_base,
2147           scope  => 'sub',
2148           attrs  => ['dn', 'FAIstate', 'objectClass'],
2149           filter => "$search"
2150           );
2152           if ($mesg->count) {
2153                   my @entries = $mesg->entries;
2154                   if (0 == @entries) {
2155                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2156                   }
2158                   foreach my $entry (@entries) {
2159                           # Only modify entry if it is not set to '$state'
2160                           if ($entry->get_value("FAIstate") ne "$state"){
2161                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2162                                   my $result;
2163                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2164                                   if (exists $tmp{'FAIobject'}){
2165                                           if ($state eq ''){
2166                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2167                                                           delete => [ FAIstate => [] ] ]);
2168                                           } else {
2169                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2170                                                           replace => [ FAIstate => $state ] ]);
2171                                           }
2172                                   } elsif ($state ne ''){
2173                                           $result= $ldap_handle->modify($entry->dn, changes => [
2174                                                   add     => [ objectClass => 'FAIobject' ],
2175                                                   add     => [ FAIstate => $state ] ]);
2176                                   }
2178                                   # Errors?
2179                                   if ($result->code){
2180                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2181                                   }
2182                           } else {
2183                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2184                           }  
2185                   }
2186           } else {
2187                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2188           }
2190     # if no ldap handle defined
2191     } else {
2192         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2193     }
2195         return;
2199 sub change_goto_state {
2200     my ($st, $targets, $session_id) = @_;
2201     $session_id = 0  if not defined $session_id;
2203     # Switch on or off?
2204     my $state= $st eq 'active' ? 'active': 'locked';
2206     my $ldap_handle = &get_ldap_handle($session_id);
2207     if( defined($ldap_handle) ) {
2209       # Build search filter for hosts
2210       my $search= "(&(objectClass=GOhard)";
2211       foreach (@{$targets}){
2212         $search.= "(macAddress=$_)";
2213       }
2214       $search.= ")";
2216       # If there's any host inside of the search string, procress them
2217       if (!($search =~ /macAddress/)){
2218         return;
2219       }
2221       # Perform search for Unit Tag
2222       my $mesg = $ldap_handle->search(
2223           base   => $ldap_base,
2224           scope  => 'sub',
2225           attrs  => ['dn', 'gotoMode'],
2226           filter => "$search"
2227           );
2229       if ($mesg->count) {
2230         my @entries = $mesg->entries;
2231         foreach my $entry (@entries) {
2233           # Only modify entry if it is not set to '$state'
2234           if ($entry->get_value("gotoMode") ne $state){
2236             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2237             my $result;
2238             $result= $ldap_handle->modify($entry->dn, changes => [
2239                                                 replace => [ gotoMode => $state ] ]);
2241             # Errors?
2242             if ($result->code){
2243               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2244             }
2246           }
2247         }
2248       } else {
2249                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2250           }
2252     }
2256 sub run_recreate_packages_db {
2257     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2258     my $session_id = $session->ID;
2259         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2260         $kernel->yield('create_fai_release_db', $fai_release_tn);
2261         $kernel->yield('create_fai_server_db', $fai_server_tn);
2262         return;
2266 sub run_create_fai_server_db {
2267     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2268     my $session_id = $session->ID;
2269     my $task = POE::Wheel::Run->new(
2270             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2271             StdoutEvent  => "session_run_result",
2272             StderrEvent  => "session_run_debug",
2273             CloseEvent   => "session_run_done",
2274             );
2276     $heap->{task}->{ $task->ID } = $task;
2277     return;
2281 sub create_fai_server_db {
2282         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2283         my $result;
2285         if (not defined $session_id) { $session_id = 0; }
2286         my $ldap_handle = &get_ldap_handle();
2287         if(defined($ldap_handle)) {
2288                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2289                 my $mesg= $ldap_handle->search(
2290                         base   => $ldap_base,
2291                         scope  => 'sub',
2292                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2293                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2294                 );
2295                 if($mesg->{'resultCode'} == 0 &&
2296                         $mesg->count != 0) {
2297                         foreach my $entry (@{$mesg->{entries}}) {
2298                                 if($entry->exists('FAIrepository')) {
2299                                         # Add an entry for each Repository configured for server
2300                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2301                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2302                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2303                                                 $result= $fai_server_db->add_dbentry( { 
2304                                                                 table => $table_name,
2305                                                                 primkey => ['server', 'fai_release', 'tag'],
2306                                                                 server => $tmp_url,
2307                                                                 fai_release => $tmp_release,
2308                                                                 sections => $tmp_sections,
2309                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2310                                                         } );
2311                                         }
2312                                 }
2313                         }
2314                 }
2315                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2317                 # TODO: Find a way to post the 'create_packages_list_db' event
2318                 if(not defined($dont_create_packages_list)) {
2319                         &create_packages_list_db(undef, undef, $session_id);
2320                 }
2321         }       
2323         $ldap_handle->disconnect;
2324         return $result;
2328 sub run_create_fai_release_db {
2329         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2330         my $session_id = $session->ID;
2331         my $task = POE::Wheel::Run->new(
2332                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2333                 StdoutEvent  => "session_run_result",
2334                 StderrEvent  => "session_run_debug",
2335                 CloseEvent   => "session_run_done",
2336         );
2338         $heap->{task}->{ $task->ID } = $task;
2339         return;
2343 sub create_fai_release_db {
2344         my ($table_name, $session_id) = @_;
2345         my $result;
2347         # used for logging
2348         if (not defined $session_id) { $session_id = 0; }
2350         my $ldap_handle = &get_ldap_handle();
2351         if(defined($ldap_handle)) {
2352                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2353                 my $mesg= $ldap_handle->search(
2354                         base   => $ldap_base,
2355                         scope  => 'sub',
2356                         attrs  => [],
2357                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2358                 );
2359                 if($mesg->{'resultCode'} == 0 &&
2360                         $mesg->count != 0) {
2361                         # Walk through all possible FAI container ou's
2362                         my @sql_list;
2363                         my $timestamp= &get_time();
2364                         foreach my $ou (@{$mesg->{entries}}) {
2365                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2366                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2367                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2368                                         if(@tmp_array) {
2369                                                 foreach my $entry (@tmp_array) {
2370                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2371                                                                 my $sql= 
2372                                                                 "INSERT INTO $table_name "
2373                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2374                                                                 .$timestamp.","
2375                                                                 ."'".$entry->{'release'}."',"
2376                                                                 ."'".$entry->{'class'}."',"
2377                                                                 ."'".$entry->{'type'}."',"
2378                                                                 ."'".$entry->{'state'}."')";
2379                                                                 push @sql_list, $sql;
2380                                                         }
2381                                                 }
2382                                         }
2383                                 }
2384                         }
2386                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2387                         if(@sql_list) {
2388                                 unshift @sql_list, "DELETE FROM $table_name";
2389                                 $fai_release_db->exec_statementlist(\@sql_list);
2390                         }
2391                         daemon_log("$session_id DEBUG: Done with inserting",7);
2392                 }
2393                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2394         }
2395         $ldap_handle->disconnect;
2396         return $result;
2399 sub get_fai_types {
2400         my $tmp_classes = shift || return undef;
2401         my @result;
2403         foreach my $type(keys %{$tmp_classes}) {
2404                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2405                         my $entry = {
2406                                 type => $type,
2407                                 state => $tmp_classes->{$type}[0],
2408                         };
2409                         push @result, $entry;
2410                 }
2411         }
2413         return @result;
2416 sub get_fai_state {
2417         my $result = "";
2418         my $tmp_classes = shift || return $result;
2420         foreach my $type(keys %{$tmp_classes}) {
2421                 if(defined($tmp_classes->{$type}[0])) {
2422                         $result = $tmp_classes->{$type}[0];
2423                         
2424                 # State is equal for all types in class
2425                         last;
2426                 }
2427         }
2429         return $result;
2432 sub resolve_fai_classes {
2433         my ($fai_base, $ldap_handle, $session_id) = @_;
2434         if (not defined $session_id) { $session_id = 0; }
2435         my $result;
2436         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2437         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2438         my $fai_classes;
2440         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2441         my $mesg= $ldap_handle->search(
2442                 base   => $fai_base,
2443                 scope  => 'sub',
2444                 attrs  => ['cn','objectClass','FAIstate'],
2445                 filter => $fai_filter,
2446         );
2447         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2449         if($mesg->{'resultCode'} == 0 &&
2450                 $mesg->count != 0) {
2451                 foreach my $entry (@{$mesg->{entries}}) {
2452                         if($entry->exists('cn')) {
2453                                 my $tmp_dn= $entry->dn();
2455                                 # Skip classname and ou dn parts for class
2456                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2458                                 # Skip classes without releases
2459                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2460                                         next;
2461                                 }
2463                                 my $tmp_cn= $entry->get_value('cn');
2464                                 my $tmp_state= $entry->get_value('FAIstate');
2466                                 my $tmp_type;
2467                                 # Get FAI type
2468                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2469                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2470                                                 $tmp_type= $oclass;
2471                                                 last;
2472                                         }
2473                                 }
2475                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2476                                         # A Subrelease
2477                                         my @sub_releases = split(/,/, $tmp_release);
2479                                         # Walk through subreleases and build hash tree
2480                                         my $hash;
2481                                         while(my $tmp_sub_release = pop @sub_releases) {
2482                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2483                                         }
2484                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2485                                 } else {
2486                                         # A branch, no subrelease
2487                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2488                                 }
2489                         } elsif (!$entry->exists('cn')) {
2490                                 my $tmp_dn= $entry->dn();
2491                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2493                                 # Skip classes without releases
2494                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2495                                         next;
2496                                 }
2498                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2499                                         # A Subrelease
2500                                         my @sub_releases= split(/,/, $tmp_release);
2502                                         # Walk through subreleases and build hash tree
2503                                         my $hash;
2504                                         while(my $tmp_sub_release = pop @sub_releases) {
2505                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2506                                         }
2507                                         # Remove the last two characters
2508                                         chop($hash);
2509                                         chop($hash);
2511                                         eval('$fai_classes->'.$hash.'= {}');
2512                                 } else {
2513                                         # A branch, no subrelease
2514                                         if(!exists($fai_classes->{$tmp_release})) {
2515                                                 $fai_classes->{$tmp_release} = {};
2516                                         }
2517                                 }
2518                         }
2519                 }
2521                 # The hash is complete, now we can honor the copy-on-write based missing entries
2522                 foreach my $release (keys %$fai_classes) {
2523                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2524                 }
2525         }
2526         return $result;
2529 sub apply_fai_inheritance {
2530        my $fai_classes = shift || return {};
2531        my $tmp_classes;
2533        # Get the classes from the branch
2534        foreach my $class (keys %{$fai_classes}) {
2535                # Skip subreleases
2536                if($class =~ /^ou=.*$/) {
2537                        next;
2538                } else {
2539                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2540                }
2541        }
2543        # Apply to each subrelease
2544        foreach my $subrelease (keys %{$fai_classes}) {
2545                if($subrelease =~ /ou=/) {
2546                        foreach my $tmp_class (keys %{$tmp_classes}) {
2547                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2548                                        $fai_classes->{$subrelease}->{$tmp_class} =
2549                                        deep_copy($tmp_classes->{$tmp_class});
2550                                } else {
2551                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2552                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2553                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2554                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2555                                                }
2556                                        }
2557                                }
2558                        }
2559                }
2560        }
2562        # Find subreleases in deeper levels
2563        foreach my $subrelease (keys %{$fai_classes}) {
2564                if($subrelease =~ /ou=/) {
2565                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2566                                if($subsubrelease =~ /ou=/) {
2567                                        apply_fai_inheritance($fai_classes->{$subrelease});
2568                                }
2569                        }
2570                }
2571        }
2573        return $fai_classes;
2576 sub get_fai_release_entries {
2577         my $tmp_classes = shift || return;
2578         my $parent = shift || "";
2579         my @result = shift || ();
2581         foreach my $entry (keys %{$tmp_classes}) {
2582                 if(defined($entry)) {
2583                         if($entry =~ /^ou=.*$/) {
2584                                 my $release_name = $entry;
2585                                 $release_name =~ s/ou=//g;
2586                                 if(length($parent)>0) {
2587                                         $release_name = $parent."/".$release_name;
2588                                 }
2589                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2590                                 foreach my $bufentry(@bufentries) {
2591                                         push @result, $bufentry;
2592                                 }
2593                         } else {
2594                                 my @types = get_fai_types($tmp_classes->{$entry});
2595                                 foreach my $type (@types) {
2596                                         push @result, 
2597                                         {
2598                                                 'class' => $entry,
2599                                                 'type' => $type->{'type'},
2600                                                 'release' => $parent,
2601                                                 'state' => $type->{'state'},
2602                                         };
2603                                 }
2604                         }
2605                 }
2606         }
2608         return @result;
2611 sub deep_copy {
2612         my $this = shift;
2613         if (not ref $this) {
2614                 $this;
2615         } elsif (ref $this eq "ARRAY") {
2616                 [map deep_copy($_), @$this];
2617         } elsif (ref $this eq "HASH") {
2618                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2619         } else { die "what type is $_?" }
2623 sub session_run_result {
2624     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2625     $kernel->sig(CHLD => "child_reap");
2628 sub session_run_debug {
2629     my $result = $_[ARG0];
2630     print STDERR "$result\n";
2633 sub session_run_done {
2634     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2635     delete $heap->{task}->{$task_id};
2639 sub create_sources_list {
2640         my $session_id = shift;
2641         my $ldap_handle = &main::get_ldap_handle;
2642         my $result="/tmp/gosa_si_tmp_sources_list";
2644         # Remove old file
2645         if(stat($result)) {
2646                 unlink($result);
2647                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2648         }
2650         my $fh;
2651         open($fh, ">$result");
2652         if (not defined $fh) {
2653                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2654                 return undef;
2655         }
2656         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2657                 my $mesg=$ldap_handle->search(
2658                         base    => $main::ldap_server_dn,
2659                         scope   => 'base',
2660                         attrs   => 'FAIrepository',
2661                         filter  => 'objectClass=FAIrepositoryServer'
2662                 );
2663                 if($mesg->count) {
2664                         foreach my $entry(@{$mesg->{'entries'}}) {
2665                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2666                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2667                                         my $line = "deb $server $release";
2668                                         $sections =~ s/,/ /g;
2669                                         $line.= " $sections";
2670                                         print $fh $line."\n";
2671                                 }
2672                         }
2673                 }
2674         } else {
2675                 if (defined $main::ldap_server_dn){
2676                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2677                 } else {
2678                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2679                 }
2680         }
2681         close($fh);
2683         return $result;
2687 sub run_create_packages_list_db {
2688     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2689         my $session_id = $session->ID;
2691         my $task = POE::Wheel::Run->new(
2692                                         Priority => +20,
2693                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2694                                         StdoutEvent  => "session_run_result",
2695                                         StderrEvent  => "session_run_debug",
2696                                         CloseEvent   => "session_run_done",
2697                                         );
2698         $heap->{task}->{ $task->ID } = $task;
2702 sub create_packages_list_db {
2703         my ($ldap_handle, $sources_file, $session_id) = @_;
2704         
2705         # it should not be possible to trigger a recreation of packages_list_db
2706         # while packages_list_db is under construction, so set flag packages_list_under_construction
2707         # which is tested befor recreation can be started
2708         if (-r $packages_list_under_construction) {
2709                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2710                 return;
2711         } else {
2712                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2713                 # set packages_list_under_construction to true
2714                 system("touch $packages_list_under_construction");
2715                 @packages_list_statements=();
2716         }
2718         if (not defined $session_id) { $session_id = 0; }
2719         if (not defined $ldap_handle) { 
2720                 $ldap_handle= &get_ldap_handle();
2722                 if (not defined $ldap_handle) {
2723                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2724                         unlink($packages_list_under_construction);
2725                         return;
2726                 }
2727         }
2728         if (not defined $sources_file) { 
2729                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2730                 $sources_file = &create_sources_list($session_id);
2731         }
2733         if (not defined $sources_file) {
2734                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2735                 unlink($packages_list_under_construction);
2736                 return;
2737         }
2739         my $line;
2741         open(CONFIG, "<$sources_file") or do {
2742                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2743                 unlink($packages_list_under_construction);
2744                 return;
2745         };
2747         # Read lines
2748         while ($line = <CONFIG>){
2749                 # Unify
2750                 chop($line);
2751                 $line =~ s/^\s+//;
2752                 $line =~ s/^\s+/ /;
2754                 # Strip comments
2755                 $line =~ s/#.*$//g;
2757                 # Skip empty lines
2758                 if ($line =~ /^\s*$/){
2759                         next;
2760                 }
2762                 # Interpret deb line
2763                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2764                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2765                         my $section;
2766                         foreach $section (split(' ', $sections)){
2767                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2768                         }
2769                 }
2770         }
2772         close (CONFIG);
2775         if(keys(%repo_dirs)) {
2776                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2777                 &main::strip_packages_list_statements();
2778                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2779         }
2780         unlink($packages_list_under_construction);
2781         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2782         return;
2785 # This function should do some intensive task to minimize the db-traffic
2786 sub strip_packages_list_statements {
2787     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2788         my @new_statement_list=();
2789         my $hash;
2790         my $insert_hash;
2791         my $update_hash;
2792         my $delete_hash;
2793         my $local_timestamp=get_time();
2795         foreach my $existing_entry (@existing_entries) {
2796                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2797         }
2799         foreach my $statement (@packages_list_statements) {
2800                 if($statement =~ /^INSERT/i) {
2801                         # Assign the values from the insert statement
2802                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2803                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2804                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2805                                 # If section or description has changed, update the DB
2806                                 if( 
2807                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2808                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2809                                 ) {
2810                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2811                                 }
2812                         } else {
2813                                 # Insert a non-existing entry to db
2814                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2815                         }
2816                 } elsif ($statement =~ /^UPDATE/i) {
2817                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2818                         /^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;
2819                         foreach my $distribution (keys %{$hash}) {
2820                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2821                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2822                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2823                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2824                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2825                                                 my $section;
2826                                                 my $description;
2827                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2828                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2829                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2830                                                 }
2831                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2832                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2833                                                 }
2834                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2835                                         }
2836                                 }
2837                         }
2838                 }
2839         }
2841         # TODO: Check for orphaned entries
2843         # unroll the insert_hash
2844         foreach my $distribution (keys %{$insert_hash}) {
2845                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2846                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2847                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2848                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2849                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2850                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2851                                 ."'$local_timestamp')";
2852                         }
2853                 }
2854         }
2856         # unroll the update hash
2857         foreach my $distribution (keys %{$update_hash}) {
2858                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2859                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2860                                 my $set = "";
2861                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2862                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2863                                 }
2864                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2865                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2866                                 }
2867                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2868                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2869                                 }
2870                                 if(defined($set) and length($set) > 0) {
2871                                         $set .= "timestamp = '$local_timestamp'";
2872                                 } else {
2873                                         next;
2874                                 }
2875                                 push @new_statement_list, 
2876                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2877                                         ." distribution = '$distribution'"
2878                                         ." AND package = '$package'"
2879                                         ." AND version = '$version'";
2880                         }
2881                 }
2882         }
2884         @packages_list_statements = @new_statement_list;
2888 sub parse_package_info {
2889     my ($baseurl, $dist, $section, $session_id)= @_;
2890     my ($package);
2891     if (not defined $session_id) { $session_id = 0; }
2892     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2893     $repo_dirs{ "${repo_path}/pool" } = 1;
2895     foreach $package ("Packages.gz"){
2896         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2897         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2898         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2899     }
2900     
2904 sub get_package {
2905     my ($url, $dest, $session_id)= @_;
2906     if (not defined $session_id) { $session_id = 0; }
2908     my $tpath = dirname($dest);
2909     -d "$tpath" || mkpath "$tpath";
2911     # This is ugly, but I've no time to take a look at "how it works in perl"
2912     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2913         system("gunzip -cd '$dest' > '$dest.in'");
2914         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2915         unlink($dest);
2916         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2917     } else {
2918         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2919     }
2920     return 0;
2924 sub parse_package {
2925     my ($path, $dist, $srv_path, $session_id)= @_;
2926     if (not defined $session_id) { $session_id = 0;}
2927     my ($package, $version, $section, $description);
2928     my $PACKAGES;
2929     my $timestamp = &get_time();
2931     if(not stat("$path.in")) {
2932         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2933         return;
2934     }
2936     open($PACKAGES, "<$path.in");
2937     if(not defined($PACKAGES)) {
2938         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2939         return;
2940     }
2942     # Read lines
2943     while (<$PACKAGES>){
2944         my $line = $_;
2945         # Unify
2946         chop($line);
2948         # Use empty lines as a trigger
2949         if ($line =~ /^\s*$/){
2950             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2951             push(@packages_list_statements, $sql);
2952             $package = "none";
2953             $version = "none";
2954             $section = "none";
2955             $description = "none"; 
2956             next;
2957         }
2959         # Trigger for package name
2960         if ($line =~ /^Package:\s/){
2961             ($package)= ($line =~ /^Package: (.*)$/);
2962             next;
2963         }
2965         # Trigger for version
2966         if ($line =~ /^Version:\s/){
2967             ($version)= ($line =~ /^Version: (.*)$/);
2968             next;
2969         }
2971         # Trigger for description
2972         if ($line =~ /^Description:\s/){
2973             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2974             next;
2975         }
2977         # Trigger for section
2978         if ($line =~ /^Section:\s/){
2979             ($section)= ($line =~ /^Section: (.*)$/);
2980             next;
2981         }
2983         # Trigger for filename
2984         if ($line =~ /^Filename:\s/){
2985             my ($filename) = ($line =~ /^Filename: (.*)$/);
2986             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2987             next;
2988         }
2989     }
2991     close( $PACKAGES );
2992     unlink( "$path.in" );
2996 sub store_fileinfo {
2997     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2999     my %fileinfo = (
3000         'package' => $package,
3001         'dist' => $dist,
3002         'version' => $vers,
3003     );
3005     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3009 sub cleanup_and_extract {
3010         my $fileinfo = $repo_files{ $File::Find::name };
3012         if( defined $fileinfo ) {
3013                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3014                 my $sql;
3015                 my $package = $fileinfo->{ 'package' };
3016                 my $newver = $fileinfo->{ 'version' };
3018                 mkpath($dir);
3019                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3021                 if( -f "$dir/DEBIAN/templates" ) {
3023                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3025                         my $tmpl= ""; {
3026                                 local $/=undef;
3027                                 open FILE, "$dir/DEBIAN/templates";
3028                                 $tmpl = &encode_base64(<FILE>);
3029                                 close FILE;
3030                         }
3031                         rmtree("$dir/DEBIAN/templates");
3033                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3034                         push @packages_list_statements, $sql;
3035                 }
3036         }
3038         return;
3042 sub register_at_foreign_servers {   
3043     my ($kernel) = $_[KERNEL];
3045     # hole alle bekannten server aus known_server_db
3046     my $server_sql = "SELECT * FROM $known_server_tn";
3047     my $server_res = $known_server_db->exec_statement($server_sql);
3049     # no entries in known_server_db
3050     if (not ref(@$server_res[0]) eq "ARRAY") { 
3051         # TODO
3052     }
3054     # detect already connected clients
3055     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3056     my $client_res = $known_clients_db->exec_statement($client_sql);
3058     # send my server details to all other gosa-si-server within the network
3059     foreach my $hit (@$server_res) {
3060         my $hostname = @$hit[0];
3061         my $hostkey = &create_passwd;
3063         # add already connected clients to registration message 
3064         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3065         &add_content2xml_hash($myhash, 'key', $hostkey);
3066         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3068         # add locally loaded gosa-si modules to registration message
3069         my $loaded_modules = {};
3070         while (my ($package, $pck_info) = each %$known_modules) {
3071                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3072                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3073                                                         $loaded_modules->{$act_module} = ""; 
3074                                                 }
3075         }
3077         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3079         # add macaddress to registration message
3080         my ($host_ip, $host_port) = split(/:/, $hostname);
3081         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3082         my $network_interface= &get_interface_for_ip($local_ip);
3083         my $host_mac = &get_mac_for_interface($network_interface);
3084         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3085         
3086         # build registration message and send it
3087         my $foreign_server_msg = &create_xml_string($myhash);
3088         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3089     }
3090     
3091     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3092     return;
3096 #==== MAIN = main ==============================================================
3097 #  parse commandline options
3098 Getopt::Long::Configure( "bundling" );
3099 GetOptions("h|help" => \&usage,
3100         "c|config=s" => \$cfg_file,
3101         "f|foreground" => \$foreground,
3102         "v|verbose+" => \$verbose,
3103         "no-arp+" => \$no_arp,
3104            );
3106 #  read and set config parameters
3107 &check_cmdline_param ;
3108 &read_configfile($cfg_file, %cfg_defaults);
3109 &check_pid;
3111 $SIG{CHLD} = 'IGNORE';
3113 # forward error messages to logfile
3114 if( ! $foreground ) {
3115   open( STDIN,  '+>/dev/null' );
3116   open( STDOUT, '+>&STDIN'    );
3117   open( STDERR, '+>&STDIN'    );
3120 # Just fork, if we are not in foreground mode
3121 if( ! $foreground ) { 
3122     chdir '/'                 or die "Can't chdir to /: $!";
3123     $pid = fork;
3124     setsid                    or die "Can't start a new session: $!";
3125     umask 0;
3126 } else { 
3127     $pid = $$; 
3130 # Do something useful - put our PID into the pid_file
3131 if( 0 != $pid ) {
3132     open( LOCK_FILE, ">$pid_file" );
3133     print LOCK_FILE "$pid\n";
3134     close( LOCK_FILE );
3135     if( !$foreground ) { 
3136         exit( 0 ) 
3137     };
3140 # parse head url and revision from svn
3141 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3142 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3143 $server_headURL = defined $1 ? $1 : 'unknown' ;
3144 $server_revision = defined $2 ? $2 : 'unknown' ;
3145 if ($server_headURL =~ /\/tag\// || 
3146         $server_headURL =~ /\/branches\// ) {
3147     $server_status = "stable"; 
3148 } else {
3149     $server_status = "developmental" ;
3152 # Prepare log file
3153 $root_uid = getpwnam('root');
3154 $adm_gid = getgrnam('adm');
3155 chmod(0640, $log_file);
3156 chown($root_uid, $adm_gid, $log_file);
3157 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3159 daemon_log(" ", 1);
3160 daemon_log("$0 started!", 1);
3161 daemon_log("status: $server_status", 1);
3162 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3164 # connect to incoming_db
3165 unlink($incoming_file_name);
3166 $incoming_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3167 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3169 # connect to gosa-si job queue
3170 unlink($job_queue_file_name);  ## just for debugging
3171 $job_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3172 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3173 chmod(0660, $job_queue_file_name);
3174 chown($root_uid, $adm_gid, $job_queue_file_name);
3176 # connect to known_clients_db
3177 unlink($known_clients_file_name);   ## just for debugging
3178 $known_clients_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3179 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3180 chmod(0660, $known_clients_file_name);
3181 chown($root_uid, $adm_gid, $known_clients_file_name);
3183 # connect to foreign_clients_db
3184 unlink($foreign_clients_file_name);
3185 $foreign_clients_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3186 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3187 chmod(0660, $foreign_clients_file_name);
3188 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3190 # connect to known_server_db
3191 unlink($known_server_file_name);
3192 $known_server_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3193 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3194 chmod(0660, $known_server_file_name);
3195 chown($root_uid, $adm_gid, $known_server_file_name);
3197 # connect to login_usr_db
3198 unlink($login_users_file_name);
3199 $login_users_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3200 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3201 chmod(0660, $login_users_file_name);
3202 chown($root_uid, $adm_gid, $login_users_file_name);
3204 # connect to fai_server_db 
3205 unlink($fai_server_file_name);
3206 $fai_server_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3207 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3208 chmod(0660, $fai_server_file_name);
3209 chown($root_uid, $adm_gid, $fai_server_file_name);
3211 # connect to fai_release_db
3212 unlink($fai_release_file_name);
3213 $fai_release_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3214 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3215 chmod(0660, $fai_release_file_name);
3216 chown($root_uid, $adm_gid, $fai_release_file_name);
3218 # connect to packages_list_db
3219 #unlink($packages_list_file_name);
3220 unlink($packages_list_under_construction);
3221 $packages_list_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3222 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3223 chmod(0660, $packages_list_file_name);
3224 chown($root_uid, $adm_gid, $packages_list_file_name);
3226 # connect to messaging_db
3227 unlink($messaging_file_name);
3228 $messaging_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3229 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3230 chmod(0660, $messaging_file_name);
3231 chown($root_uid, $adm_gid, $messaging_file_name);
3234 # create xml object used for en/decrypting
3235 $xml = new XML::Simple();
3238 # foreign servers 
3239 my @foreign_server_list;
3241 # add foreign server from cfg file
3242 if ($foreign_server_string ne "") {
3243     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3244     foreach my $foreign_server (@cfg_foreign_server_list) {
3245         push(@foreign_server_list, $foreign_server);
3246     }
3248     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3251 # Perform a DNS lookup for server registration if flag is true
3252 if ($dns_lookup eq "true") {
3253     # Add foreign server from dns
3254     my @tmp_servers;
3255     if (not $server_domain) {
3256         # Try our DNS Searchlist
3257         for my $domain(get_dns_domains()) {
3258             chomp($domain);
3259             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3260             if(@$tmp_domains) {
3261                 for my $tmp_server(@$tmp_domains) {
3262                     push @tmp_servers, $tmp_server;
3263                 }
3264             }
3265         }
3266         if(@tmp_servers && length(@tmp_servers)==0) {
3267             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3268         }
3269     } else {
3270         @tmp_servers = &get_server_addresses($server_domain);
3271         if( 0 == @tmp_servers ) {
3272             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3273         }
3274     }
3276     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3278     foreach my $server (@tmp_servers) { 
3279         unshift(@foreign_server_list, $server); 
3280     }
3281 } else {
3282     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3286 # eliminate duplicate entries
3287 @foreign_server_list = &del_doubles(@foreign_server_list);
3288 my $all_foreign_server = join(", ", @foreign_server_list);
3289 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3291 # add all found foreign servers to known_server
3292 my $act_timestamp = &get_time();
3293 foreach my $foreign_server (@foreign_server_list) {
3295         # do not add myself to known_server_db
3296         if (&is_local($foreign_server)) { next; }
3297         ######################################
3299     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3300             primkey=>['hostname'],
3301             hostname=>$foreign_server,
3302             macaddress=>"",
3303             status=>'not_jet_registered',
3304             hostkey=>"none",
3305             loaded_modules => "none", 
3306             timestamp=>$act_timestamp,
3307             } );
3311 # Import all modules
3312 &import_modules;
3314 # Check wether all modules are gosa-si valid passwd check
3315 &password_check;
3317 # Prepare for using Opsi 
3318 if ($opsi_enabled eq "true") {
3319     use JSON::RPC::Client;
3320     use XML::Quote qw(:all);
3321     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3322     $opsi_client = new JSON::RPC::Client;
3326 POE::Component::Server::TCP->new(
3327         Alias => "TCP_SERVER",
3328         Port => $server_port,
3329         ClientInput => sub {
3330                 my ($kernel, $input) = @_[KERNEL, ARG0];
3331                 push(@tasks, $input);
3332                 push(@msgs_to_decrypt, $input);
3333                 $kernel->yield("msg_to_decrypt");
3334         },
3335         InlineStates => {
3336                 msg_to_decrypt => \&msg_to_decrypt,
3337                 next_task => \&next_task,
3338                 task_result => \&handle_task_result,
3339                 task_done   => \&handle_task_done,
3340                 task_debug  => \&handle_task_debug,
3341                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3342         }
3343 );
3345 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3347 # create session for repeatedly checking the job queue for jobs
3348 POE::Session->create(
3349         inline_states => {
3350                 _start => \&session_start,
3351         register_at_foreign_servers => \&register_at_foreign_servers,
3352         sig_handler => \&sig_handler,
3353         next_task => \&next_task,
3354         task_result => \&handle_task_result,
3355         task_done   => \&handle_task_done,
3356         task_debug  => \&handle_task_debug,
3357         watch_for_next_tasks => \&watch_for_next_tasks,
3358         watch_for_new_messages => \&watch_for_new_messages,
3359         watch_for_delivery_messages => \&watch_for_delivery_messages,
3360         watch_for_done_messages => \&watch_for_done_messages,
3361                 watch_for_new_jobs => \&watch_for_new_jobs,
3362         watch_for_modified_jobs => \&watch_for_modified_jobs,
3363         watch_for_done_jobs => \&watch_for_done_jobs,
3364         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3365         watch_for_old_known_clients => \&watch_for_old_known_clients,
3366         create_packages_list_db => \&run_create_packages_list_db,
3367         create_fai_server_db => \&run_create_fai_server_db,
3368         create_fai_release_db => \&run_create_fai_release_db,
3369                 recreate_packages_db => \&run_recreate_packages_db,
3370         session_run_result => \&session_run_result,
3371         session_run_debug => \&session_run_debug,
3372         session_run_done => \&session_run_done,
3373         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3374         }
3375 );
3378 POE::Kernel->run();
3379 exit;