Code

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