Code

08df17437c0e22b9bbd32312149b8bdb494b090e
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
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 $known_modules;
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 # holds all incoming decrypted messages
120 our $incoming_db;
121 our $incoming_tn = 'incoming';
122 my $incoming_file_name;
123 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
124         "timestamp DEFAULT 'none'", 
125         "headertag DEFAULT 'none'",
126                 "targettag DEFAULT 'none'",
127         "xmlmessage DEFAULT 'none'",
128         "module DEFAULT 'none'",
129         "sessionid DEFAULT '0'",
130         );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
137                 "timestamp DEFAULT 'none'", 
138                 "status DEFAULT 'none'", 
139                 "result DEFAULT 'none'", 
140                 "progress DEFAULT 'none'", 
141         "headertag DEFAULT 'none'", 
142                 "targettag DEFAULT 'none'", 
143                 "xmlmessage DEFAULT 'none'", 
144                 "macaddress DEFAULT 'none'",
145                 "plainname DEFAULT 'none'",
146         "siserver DEFAULT 'none'",
147         "modified DEFAULT '0'",
148                 );
150 # holds all other gosa-si-server
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients"; 
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client 
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server"; 
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release"; 
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386"; 
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging"; 
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
197         "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai"; 
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 # loop delay for job queue to look for opsi jobs
210 my $job_queue_opsi_delay = 10;
211 our $opsi_client;
212 our $opsi_url;
213  
214 # Lifetime of logged in user information. If no update information comes after n seconds, 
215 # the user is expeceted to be no longer logged in or the host is no longer running. Because
216 # of this, the user is deleted from login_users_db
217 our $logged_in_user_date_of_expiry = 600;
220 %cfg_defaults = (
221 "general" => {
222     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
223     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
224     },
225 "server" => {
226     "ip"                    => [\$server_ip, "0.0.0.0"],
227     "port"                  => [\$server_port, "20081"],
228     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
229     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
230     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
231     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
232     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
233     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
234     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
235     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
236     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
237     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
238     "repo-path"             => [\$repo_path, '/srv/www/repository'],
239     "ldap-uri"              => [\$ldap_uri, ""],
240     "ldap-base"             => [\$ldap_base, ""],
241     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
242     "ldap-admin-password"   => [\$ldap_admin_password, ""],
243     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
244     "max-clients"           => [\$max_clients, 10],
245     "wol-password"          => [\$wake_on_lan_passwd, ""],
246     },
247 "GOsaPackages" => {
248     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
249     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
250     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
251     "key" => [\$GosaPackages_key, "none"],
252                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
253     },
254 "ClientPackages" => {
255     "key" => [\$ClientPackages_key, "none"],
256     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
257     },
258 "ServerPackages"=> {
259     "address"      => [\$foreign_server_string, ""],
260     "dns-lookup"            => [\$dns_lookup, "true"],
261     "domain"  => [\$server_domain, ""],
262     "key"     => [\$ServerPackages_key, "none"],
263     "key-lifetime" => [\$foreign_servers_register_delay, 120],
264     "job-synchronization-enabled" => [\$job_synchronization, "true"],
265     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
266     },
267 "ArpHandler" => {
268     "enabled"   => [\$arp_enabled, "true"],
269     "interface" => [\$arp_interface, "all"],
270         },
271 "Opsi" => {
272     "enabled"  => [\$opsi_enabled, "false"], 
273     "server"   => [\$opsi_server, "localhost"],
274     "admin"    => [\$opsi_admin, "opsi-admin"],
275     "password" => [\$opsi_password, "secret"],
276    },
278 );
281 #===  FUNCTION  ================================================================
282 #         NAME:  usage
283 #   PARAMETERS:  nothing
284 #      RETURNS:  nothing
285 #  DESCRIPTION:  print out usage text to STDERR
286 #===============================================================================
287 sub usage {
288     print STDERR << "EOF" ;
289 usage: $prg [-hvf] [-c config]
291            -h        : this (help) message
292            -c <file> : config file
293            -f        : foreground, process will not be forked to background
294            -v        : be verbose (multiple to increase verbosity)
295            -no-arp   : starts $prg without connection to arp module
296  
297 EOF
298     print "\n" ;
302 #===  FUNCTION  ================================================================
303 #         NAME:  logging
304 #   PARAMETERS:  level - string - default 'info'
305 #                msg - string -
306 #                facility - string - default 'LOG_DAEMON'
307 #      RETURNS:  nothing
308 #  DESCRIPTION:  function for logging
309 #===============================================================================
310 sub daemon_log {
311     # log into log_file
312     my( $msg, $level ) = @_;
313     if(not defined $msg) { return }
314     if(not defined $level) { $level = 1 }
315     if(defined $log_file){
316         open(LOG_HANDLE, ">>$log_file");
317         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
318             print STDERR "cannot open $log_file: $!";
319             return 
320         }
321         chomp($msg);
322         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
323         if($level <= $verbose){
324             my ($seconds, $minutes, $hours, $monthday, $month,
325                     $year, $weekday, $yearday, $sommertime) = localtime(time);
326             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
327             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
328             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
329             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
330             $month = $monthnames[$month];
331             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
332             $year+=1900;
333             my $name = $prg;
335             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
336             print LOG_HANDLE $log_msg;
337             if( $foreground ) { 
338                 print STDERR $log_msg;
339             }
340         }
341         close( LOG_HANDLE );
342     }
346 #===  FUNCTION  ================================================================
347 #         NAME:  check_cmdline_param
348 #   PARAMETERS:  nothing
349 #      RETURNS:  nothing
350 #  DESCRIPTION:  validates commandline parameter
351 #===============================================================================
352 sub check_cmdline_param () {
353     my $err_config;
354     my $err_counter = 0;
355         if(not defined($cfg_file)) {
356                 $cfg_file = "/etc/gosa-si/server.conf";
357                 if(! -r $cfg_file) {
358                         $err_config = "please specify a config file";
359                         $err_counter += 1;
360                 }
361     }
362     if( $err_counter > 0 ) {
363         &usage( "", 1 );
364         if( defined( $err_config)) { print STDERR "$err_config\n"}
365         print STDERR "\n";
366         exit( -1 );
367     }
371 #===  FUNCTION  ================================================================
372 #         NAME:  check_pid
373 #   PARAMETERS:  nothing
374 #      RETURNS:  nothing
375 #  DESCRIPTION:  handels pid processing
376 #===============================================================================
377 sub check_pid {
378     $pid = -1;
379     # Check, if we are already running
380     if( open(LOCK_FILE, "<$pid_file") ) {
381         $pid = <LOCK_FILE>;
382         if( defined $pid ) {
383             chomp( $pid );
384             if( -f "/proc/$pid/stat" ) {
385                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
386                 if( $stat ) {
387                                         daemon_log("ERROR: Already running",1);
388                     close( LOCK_FILE );
389                     exit -1;
390                 }
391             }
392         }
393         close( LOCK_FILE );
394         unlink( $pid_file );
395     }
397     # create a syslog msg if it is not to possible to open PID file
398     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
399         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
400         if (open(LOCK_FILE, '<', $pid_file)
401                 && ($pid = <LOCK_FILE>))
402         {
403             chomp($pid);
404             $msg .= "(PID $pid)\n";
405         } else {
406             $msg .= "(unable to read PID)\n";
407         }
408         if( ! ($foreground) ) {
409             openlog( $0, "cons,pid", "daemon" );
410             syslog( "warning", $msg );
411             closelog();
412         }
413         else {
414             print( STDERR " $msg " );
415         }
416         exit( -1 );
417     }
420 #===  FUNCTION  ================================================================
421 #         NAME:  import_modules
422 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
423 #                are stored
424 #      RETURNS:  nothing
425 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
426 #                state is on is imported by "require 'file';"
427 #===============================================================================
428 sub import_modules {
429     daemon_log(" ", 1);
431     if (not -e $modules_path) {
432         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
433     }
435     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
436     while (defined (my $file = readdir (DIR))) {
437         if (not $file =~ /(\S*?).pm$/) {
438             next;
439         }
440                 my $mod_name = $1;
442         # ArpHandler switch
443         if( $file =~ /ArpHandler.pm/ ) {
444             if( $arp_enabled eq "false" ) { next; }
445         }
446         
447         eval { require $file; };
448         if ($@) {
449             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
450             daemon_log("$@", 5);
451                 } else {
452                         my $info = eval($mod_name.'::get_module_info()');
453                         # Only load module if get_module_info() returns a non-null object
454                         if( $info ) {
455                                 my ($input_address, $input_key, $event_hash) = @{$info};
456                                 $known_modules->{$mod_name} = $info;
457                                 daemon_log("0 INFO: module $mod_name loaded", 5);
458                         }
459                 }
460     }   
462     close (DIR);
465 #===  FUNCTION  ================================================================
466 #         NAME:  password_check
467 #   PARAMETERS:  nothing
468 #      RETURNS:  nothing
469 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
470 #                the same password
471 #===============================================================================
472 sub password_check {
473     my $passwd_hash = {};
474     while (my ($mod_name, $mod_info) = each %$known_modules) {
475         my $mod_passwd = @$mod_info[1];
476         if (not defined $mod_passwd) { next; }
477         if (not exists $passwd_hash->{$mod_passwd}) {
478             $passwd_hash->{$mod_passwd} = $mod_name;
480         # escalates critical error
481         } else {
482             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
483             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
484             exit( -1 );
485         }
486     }
491 #===  FUNCTION  ================================================================
492 #         NAME:  sig_int_handler
493 #   PARAMETERS:  signal - string - signal arose from system
494 #      RETURNS:  nothing
495 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
496 #===============================================================================
497 sub sig_int_handler {
498     my ($signal) = @_;
500 #       if (defined($ldap_handle)) {
501 #               $ldap_handle->disconnect;
502 #       }
503     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
504     
506     daemon_log("shutting down gosa-si-server", 1);
507     system("kill `ps -C gosa-si-server -o pid=`");
509 $SIG{INT} = \&sig_int_handler;
512 sub check_key_and_xml_validity {
513     my ($crypted_msg, $module_key, $session_id) = @_;
514     my $msg;
515     my $msg_hash;
516     my $error_string;
517     eval{
518         $msg = &decrypt_msg($crypted_msg, $module_key);
520         if ($msg =~ /<xml>/i){
521             $msg =~ s/\s+/ /g;  # just for better daemon_log
522             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
523             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
525             ##############
526             # check header
527             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
528             my $header_l = $msg_hash->{'header'};
529             if( 1 > @{$header_l} ) { die 'empty header tag'; }
530             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
531             my $header = @{$header_l}[0];
532             if( 0 == length $header) { die 'empty string in header tag'; }
534             ##############
535             # check source
536             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
537             my $source_l = $msg_hash->{'source'};
538             if( 1 > @{$source_l} ) { die 'empty source tag'; }
539             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
540             my $source = @{$source_l}[0];
541             if( 0 == length $source) { die 'source error'; }
543             ##############
544             # check target
545             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
546             my $target_l = $msg_hash->{'target'};
547             if( 1 > @{$target_l} ) { die 'empty target tag'; }
548         }
549     };
550     if($@) {
551         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
552         $msg = undef;
553         $msg_hash = undef;
554     }
556     return ($msg, $msg_hash);
560 sub check_outgoing_xml_validity {
561     my ($msg, $session_id) = @_;
563     my $msg_hash;
564     eval{
565         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
567         ##############
568         # check header
569         my $header_l = $msg_hash->{'header'};
570         if( 1 != @{$header_l} ) {
571             die 'no or more than one headers specified';
572         }
573         my $header = @{$header_l}[0];
574         if( 0 == length $header) {
575             die 'header has length 0';
576         }
578         ##############
579         # check source
580         my $source_l = $msg_hash->{'source'};
581         if( 1 != @{$source_l} ) {
582             die 'no or more than 1 sources specified';
583         }
584         my $source = @{$source_l}[0];
585         if( 0 == length $source) {
586             die 'source has length 0';
587         }
588         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
589                 $source =~ /^GOSA$/i ) {
590             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
591         }
592         
593         ##############
594         # check target  
595         my $target_l = $msg_hash->{'target'};
596         if( 0 == @{$target_l} ) {
597             die "no targets specified";
598         }
599         foreach my $target (@$target_l) {
600             if( 0 == length $target) {
601                 die "target has length 0";
602             }
603             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
604                     $target =~ /^GOSA$/i ||
605                     $target =~ /^\*$/ ||
606                     $target =~ /KNOWN_SERVER/i ||
607                     $target =~ /JOBDB/i ||
608                     $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 ){
609                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
610             }
611         }
612     };
613     if($@) {
614         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
615         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
616         $msg_hash = undef;
617     }
619     return ($msg_hash);
623 sub input_from_known_server {
624     my ($input, $remote_ip, $session_id) = @_ ;  
625     my ($msg, $msg_hash, $module);
627     my $sql_statement= "SELECT * FROM known_server";
628     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
630     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
631         my $host_name = $hit->{hostname};
632         if( not $host_name =~ "^$remote_ip") {
633             next;
634         }
635         my $host_key = $hit->{hostkey};
636         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
637         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
639         # check if module can open msg envelope with module key
640         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
641         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
642             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
643             daemon_log("$@", 8);
644             next;
645         }
646         else {
647             $msg = $tmp_msg;
648             $msg_hash = $tmp_msg_hash;
649             $module = "ServerPackages";
650             last;
651         }
652     }
654     if( (!$msg) || (!$msg_hash) || (!$module) ) {
655         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
656     }
657   
658     return ($msg, $msg_hash, $module);
662 sub input_from_known_client {
663     my ($input, $remote_ip, $session_id) = @_ ;  
664     my ($msg, $msg_hash, $module);
666     my $sql_statement= "SELECT * FROM known_clients";
667     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
668     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
669         my $host_name = $hit->{hostname};
670         if( not $host_name =~ /^$remote_ip:\d*$/) {
671                 next;
672                 }
673         my $host_key = $hit->{hostkey};
674         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
675         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
677         # check if module can open msg envelope with module key
678         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
680         if( (!$msg) || (!$msg_hash) ) {
681             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
682             &daemon_log("$@", 8);
683             next;
684         }
685         else {
686             $module = "ClientPackages";
687             last;
688         }
689     }
691     if( (!$msg) || (!$msg_hash) || (!$module) ) {
692         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
693     }
695     return ($msg, $msg_hash, $module);
699 sub input_from_unknown_host {
700         no strict "refs";
701         my ($input, $session_id) = @_ ;
702         my ($msg, $msg_hash, $module);
703         my $error_string;
705         my %act_modules = %$known_modules;
707         while( my ($mod, $info) = each(%act_modules)) {
709                 # check a key exists for this module
710                 my $module_key = ${$mod."_key"};
711                 if( not defined $module_key ) {
712                         if( $mod eq 'ArpHandler' ) {
713                                 next;
714                         }
715                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
716                         next;
717                 }
718                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
720                 # check if module can open msg envelope with module key
721                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
722                 if( (not defined $msg) || (not defined $msg_hash) ) {
723                         daemon_log("$session_id ERROR: no msg returned!", 2) if ((not defined $msg) || "" eq $msg);
724                         daemon_log("$session_id ERROR: no msg_hash returned!", 2) if ((not defined $msg_hash) || "" eq $msg_hash);
725                         next;
726                 } else {
727                         $module = $mod;
728                         last;
729                 }
730         }
732         if( (!$msg) || (!$msg_hash) || (!$module)) {
733                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
734         }
736         return ($msg, $msg_hash, $module);
740 sub create_ciphering {
741     my ($passwd) = @_;
742         if((!defined($passwd)) || length($passwd)==0) {
743                 $passwd = "";
744         }
745     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
746     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
747     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
748     $my_cipher->set_iv($iv);
749     return $my_cipher;
753 sub encrypt_msg {
754     my ($msg, $key) = @_;
755     my $my_cipher = &create_ciphering($key);
756     my $len;
757     {
758             use bytes;
759             $len= 16-length($msg)%16;
760     }
761     $msg = "\0"x($len).$msg;
762     $msg = $my_cipher->encrypt($msg);
763     chomp($msg = &encode_base64($msg));
764     # there are no newlines allowed inside msg
765     $msg=~ s/\n//g;
766     return $msg;
770 sub decrypt_msg {
772     my ($msg, $key) = @_ ;
773     $msg = &decode_base64($msg);
774     my $my_cipher = &create_ciphering($key);
775     $msg = $my_cipher->decrypt($msg); 
776     $msg =~ s/\0*//g;
777     return $msg;
781 sub get_encrypt_key {
782     my ($target) = @_ ;
783     my $encrypt_key;
784     my $error = 0;
786     # target can be in known_server
787     if( not defined $encrypt_key ) {
788         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
789         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
790         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
791             my $host_name = $hit->{hostname};
792             if( $host_name ne $target ) {
793                 next;
794             }
795             $encrypt_key = $hit->{hostkey};
796             last;
797         }
798     }
800     # target can be in known_client
801     if( not defined $encrypt_key ) {
802         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
803         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
804         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
805             my $host_name = $hit->{hostname};
806             if( $host_name ne $target ) {
807                 next;
808             }
809             $encrypt_key = $hit->{hostkey};
810             last;
811         }
812     }
814     return $encrypt_key;
818 #===  FUNCTION  ================================================================
819 #         NAME:  open_socket
820 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
821 #                [PeerPort] string necessary if port not appended by PeerAddr
822 #      RETURNS:  socket IO::Socket::INET
823 #  DESCRIPTION:  open a socket to PeerAddr
824 #===============================================================================
825 sub open_socket {
826     my ($PeerAddr, $PeerPort) = @_ ;
827     if(defined($PeerPort)){
828         $PeerAddr = $PeerAddr.":".$PeerPort;
829     }
830     my $socket;
831     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
832             Porto => "tcp",
833             Type => SOCK_STREAM,
834             Timeout => 5,
835             );
836     if(not defined $socket) {
837         return;
838     }
839 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
840     return $socket;
844 #sub get_local_ip_for_remote_ip {
845 #       my $remote_ip= shift;
846 #       my $result="0.0.0.0";
848 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
849 #               if($remote_ip eq "127.0.0.1") {
850 #                       $result = "127.0.0.1";
851 #               } else {
852 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
854 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
855 #                               or die "Could not open $PROC_NET_ROUTE";
857 #                       my @ifs = <PROC_NET_ROUTE>;
859 #                       close(PROC_NET_ROUTE);
861 #                       # Eat header line
862 #                       shift @ifs;
863 #                       chomp @ifs;
864 #                       foreach my $line(@ifs) {
865 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
866 #                               my $destination;
867 #                               my $mask;
868 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
869 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
870 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
871 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
872 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
873 #                                       # destination matches route, save mac and exit
874 #                                       $result= &get_ip($Iface);
875 #                                       last;
876 #                               }
877 #                       }
878 #               }
879 #       } else {
880 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
881 #       }
882 #       return $result;
883 #}
886 sub send_msg_to_target {
887     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
888     my $error = 0;
889     my $header;
890     my $timestamp = &get_time();
891     my $new_status;
892     my $act_status;
893     my ($sql_statement, $res);
894   
895     if( $msg_header ) {
896         $header = "'$msg_header'-";
897     } else {
898         $header = "";
899     }
901         # Patch the source ip
902         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
903                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
904                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
905         }
907     # encrypt xml msg
908     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
910     # opensocket
911     my $socket = &open_socket($address);
912     if( !$socket ) {
913         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
914         $error++;
915     }
916     
917     if( $error == 0 ) {
918         # send xml msg
919         print $socket $crypted_msg."\n";
921         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
922         daemon_log("$session_id DEBUG: message:\n$msg", 9);
923         
924     }
926     # close socket in any case
927     if( $socket ) {
928         close $socket;
929     }
931     if( $error > 0 ) { $new_status = "down"; }
932     else { $new_status = $msg_header; }
935     # known_clients
936     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
937     $res = $known_clients_db->select_dbentry($sql_statement);
938     if( keys(%$res) == 1) {
939         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
940         if ($act_status eq "down" && $new_status eq "down") {
941             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
942             $res = $known_clients_db->del_dbentry($sql_statement);
943             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
944         } else { 
945             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
946             $res = $known_clients_db->update_dbentry($sql_statement);
947             if($new_status eq "down"){
948                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
949             } else {
950                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
951             }
952         }
953     }
955     # known_server
956     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
957     $res = $known_server_db->select_dbentry($sql_statement);
958     if( keys(%$res) == 1) {
959         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
960         if ($act_status eq "down" && $new_status eq "down") {
961             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
962             $res = $known_server_db->del_dbentry($sql_statement);
963             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
964         } 
965         else { 
966             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
967             $res = $known_server_db->update_dbentry($sql_statement);
968             if($new_status eq "down"){
969                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
970             } else {
971                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
972             }
973         }
974     }
975     return $error; 
979 sub update_jobdb_status_for_send_msgs {
980     my ($answer, $error) = @_;
981     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
982         my $jobdb_id = $1;
983             
984         # sending msg faild
985         if( $error ) {
986             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
987                 my $sql_statement = "UPDATE $job_queue_tn ".
988                     "SET status='error', result='can not deliver msg, please consult log file' ".
989                     "WHERE id=$jobdb_id";
990                 my $res = $job_db->update_dbentry($sql_statement);
991             }
993         # sending msg was successful
994         } else {
995             my $sql_statement = "UPDATE $job_queue_tn ".
996                 "SET status='done' ".
997                 "WHERE id=$jobdb_id AND status='processed'";
998             my $res = $job_db->update_dbentry($sql_statement);
999         }
1000     }
1004 sub sig_handler {
1005         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1006         daemon_log("0 INFO got signal '$signal'", 1); 
1007         $kernel->sig_handled();
1008         return;
1012 sub msg_to_decrypt {
1013     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1014     my $session_id = $session->ID;
1015     my ($msg, $msg_hash, $module);
1016     my $error = 0;
1018     # hole neue msg aus @msgs_to_decrypt
1019     my $next_msg = shift @msgs_to_decrypt;
1020     
1021     # entschlüssle sie
1023     # msg is from a new client or gosa
1024     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1025     # msg is from a gosa-si-server
1026     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1027         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1028     }
1029     # msg is from a gosa-si-client
1030     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1031         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1032     }
1033     # an error occurred
1034     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1035         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1036         # could not understand a msg from its server the client cause a re-registering process
1037         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1038                 "' to cause a re-registering of the client if necessary", 3);
1039         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1040         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1041         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1042             my $host_name = $hit->{'hostname'};
1043             my $host_key = $hit->{'hostkey'};
1044             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1045             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1046             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1047         }
1048         $error++;
1049     }
1052     my $header;
1053     my $target;
1054     my $source;
1055     my $done = 0;
1056     my $sql;
1057     my $res;
1059     # check whether this message should be processed here
1060     if ($error == 0) {
1061         $header = @{$msg_hash->{'header'}}[0];
1062         $target = @{$msg_hash->{'target'}}[0];
1063         $source = @{$msg_hash->{'source'}}[0];
1064                 my $not_found_in_known_clients_db = 0;
1065                 my $not_found_in_known_server_db = 0;
1066                 my $not_found_in_foreign_clients_db = 0;
1067         my $local_address;
1068         my $local_mac;
1069         my ($target_ip, $target_port) = split(':', $target);
1070         
1071         # Determine the local ip address if target is an ip address
1072                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1073                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1074                 } else {
1075             $local_address = $server_address;
1076         }
1078         # Determine the local mac address if target is a mac address
1079         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) {
1080             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1081             my $network_interface= &get_interface_for_ip($loc_ip);
1082             $local_mac = &get_mac_for_interface($network_interface);
1083         } else {
1084             $local_mac = $server_mac_address;
1085         }
1087         # target and source is equal to GOSA -> process here
1088         if (not $done) {
1089             if ($target eq "GOSA" && $source eq "GOSA") {
1090                 $done = 1;                    
1091                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1092             }
1093         }
1095         # target is own address without forward_to_gosa-tag -> process here
1096         if (not $done) {
1097             #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1098             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1099                 $done = 1;
1100                 if ($source eq "GOSA") {
1101                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1102                 }
1103                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1104             }
1105         }
1107         # target is a client address in known_clients -> process here
1108                 if (not $done) {
1109                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1110                                 $res = $known_clients_db->select_dbentry($sql);
1111                                 if (keys(%$res) > 0) {
1112                                                 $done = 1; 
1113                                                 my $hostname = $res->{1}->{'hostname'};
1114                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1115                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1116                         if ($source eq "GOSA") {
1117                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1118                         }
1119                         &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1121                                 } else {
1122                                                 $not_found_in_known_clients_db = 1;
1123                                 }
1124                 }
1125         
1126         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1127         if (not $done) {
1128             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1129             my $gosa_at;
1130             my $gosa_session_id;
1131             if (($target eq $local_address) && (defined $forward_to_gosa)){
1132                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1133                 if ($gosa_at ne $local_address) {
1134                     $done = 1;
1135                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1136                 }
1137             }
1138         }
1140         # if message should be processed here -> add message to incoming_db
1141                 if ($done) {
1142                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1143                                 # so gosa-si-server knows how to process this kind of messages
1144                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1145                                                 $module = "GosaPackages";
1146                                 }
1148                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1149                                                                 primkey=>[],
1150                                                                 headertag=>$header,
1151                                                                 targettag=>$target,
1152                                                                 xmlmessage=>&encode_base64($msg),
1153                                                                 timestamp=>&get_time,
1154                                                                 module=>$module,
1155                                                                 sessionid=>$session_id,
1156                                                                 } );
1157                 }
1159         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1160         if (not $done) {
1161             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1162             my $gosa_at;
1163             my $gosa_session_id;
1164             if (($target eq $local_address) && (defined $forward_to_gosa)){
1165                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1166                 if ($gosa_at eq $local_address) {
1167                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1168                     if( defined $session_reference ) {
1169                         $heap = $session_reference->get_heap();
1170                     }
1171                     if(exists $heap->{'client'}) {
1172                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1173                         $heap->{'client'}->put($msg);
1174                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1175                     }
1176                     $done = 1;
1177                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1178                 }
1179             }
1181         }
1183         # target is a client address in foreign_clients -> forward to registration server
1184         if (not $done) {
1185             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1186             $res = $foreign_clients_db->select_dbentry($sql);
1187             if (keys(%$res) > 0) {
1188                     my $hostname = $res->{1}->{'hostname'};
1189                     my ($host_ip, $host_port) = split(/:/, $hostname);
1190                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1191                 my $regserver = $res->{1}->{'regserver'};
1192                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1193                 my $res = $known_server_db->select_dbentry($sql);
1194                 if (keys(%$res) > 0) {
1195                     my $regserver_key = $res->{1}->{'hostkey'};
1196                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1197                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1198                     if ($source eq "GOSA") {
1199                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1200                     }
1201                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1202                 }
1203                 $done = 1;
1204                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1205             } else {
1206                                 $not_found_in_foreign_clients_db = 1;
1207                         }
1208         }
1210         # target is a server address -> forward to server
1211         if (not $done) {
1212             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1213             $res = $known_server_db->select_dbentry($sql);
1214             if (keys(%$res) > 0) {
1215                 my $hostkey = $res->{1}->{'hostkey'};
1217                 if ($source eq "GOSA") {
1218                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1219                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1221                 }
1223                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1224                 $done = 1;
1225                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1226             } else {
1227                                 $not_found_in_known_server_db = 1;
1228                         }
1229         }
1231                 
1232                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1233                 if ( $not_found_in_foreign_clients_db 
1234                                                 && $not_found_in_known_server_db
1235                                                 && $not_found_in_known_clients_db) {
1236                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1237                                                                 primkey=>[],
1238                                                                 headertag=>$header,
1239                                                                 targettag=>$target,
1240                                                                 xmlmessage=>&encode_base64($msg),
1241                                                                 timestamp=>&get_time,
1242                                                                 module=>$module,
1243                                                                 sessionid=>$session_id,
1244                                                                 } );
1245                                 $done = 1;
1246                 &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);
1247                 }
1250         if (not $done) {
1251             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1252             if ($source eq "GOSA") {
1253                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1254                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1256                 my $session_reference = $kernel->ID_id_to_session($session_id);
1257                 if( defined $session_reference ) {
1258                     $heap = $session_reference->get_heap();
1259                 }
1260                 if(exists $heap->{'client'}) {
1261                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1262                     $heap->{'client'}->put($error_msg);
1263                 }
1264             }
1265         }
1267     }
1269     return;
1273 sub next_task {
1274     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1275     my $running_task = POE::Wheel::Run->new(
1276             Program => sub { process_task($session, $heap, $task) },
1277             StdioFilter => POE::Filter::Reference->new(),
1278             StdoutEvent  => "task_result",
1279             StderrEvent  => "task_debug",
1280             CloseEvent   => "task_done",
1281             );
1282     $heap->{task}->{ $running_task->ID } = $running_task;
1285 sub handle_task_result {
1286     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1287     my $client_answer = $result->{'answer'};
1288     if( $client_answer =~ s/session_id=(\d+)$// ) {
1289         my $session_id = $1;
1290         if( defined $session_id ) {
1291             my $session_reference = $kernel->ID_id_to_session($session_id);
1292             if( defined $session_reference ) {
1293                 $heap = $session_reference->get_heap();
1294             }
1295         }
1297         if(exists $heap->{'client'}) {
1298             $heap->{'client'}->put($client_answer);
1299         }
1300     }
1301     $kernel->sig(CHLD => "child_reap");
1304 sub handle_task_debug {
1305     my $result = $_[ARG0];
1306     print STDERR "$result\n";
1309 sub handle_task_done {
1310     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1311     delete $heap->{task}->{$task_id};
1314 sub process_task {
1315     no strict "refs";
1316     #CHECK: Not @_[...]?
1317     my ($session, $heap, $task) = @_;
1318     my $error = 0;
1319     my $answer_l;
1320     my ($answer_header, @answer_target_l, $answer_source);
1321     my $client_answer = "";
1323     # prepare all variables needed to process message
1324     #my $msg = $task->{'xmlmessage'};
1325     my $msg = &decode_base64($task->{'xmlmessage'});
1326     my $incoming_id = $task->{'id'};
1327     my $module = $task->{'module'};
1328     my $header =  $task->{'headertag'};
1329     my $session_id = $task->{'sessionid'};
1330     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1331     my $source = @{$msg_hash->{'source'}}[0];
1332     
1333     # set timestamp of incoming client uptodate, so client will not 
1334     # be deleted from known_clients because of expiration
1335     my $act_time = &get_time();
1336     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1337     my $res = $known_clients_db->exec_statement($sql);
1339     ######################
1340     # process incoming msg
1341     if( $error == 0) {
1342         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1343         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1344         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1346         if ( 0 < @{$answer_l} ) {
1347             my $answer_str = join("\n", @{$answer_l});
1348             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1349                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1350             }
1351             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1352         } else {
1353             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1354         }
1356     }
1357     if( !$answer_l ) { $error++ };
1359     ########
1360     # answer
1361     if( $error == 0 ) {
1363         foreach my $answer ( @{$answer_l} ) {
1364             # check outgoing msg to xml validity
1365             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1366             if( not defined $answer_hash ) { next; }
1367             
1368             $answer_header = @{$answer_hash->{'header'}}[0];
1369             @answer_target_l = @{$answer_hash->{'target'}};
1370             $answer_source = @{$answer_hash->{'source'}}[0];
1372             # deliver msg to all targets 
1373             foreach my $answer_target ( @answer_target_l ) {
1375                 # targets of msg are all gosa-si-clients in known_clients_db
1376                 if( $answer_target eq "*" ) {
1377                     # answer is for all clients
1378                     my $sql_statement= "SELECT * FROM known_clients";
1379                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1380                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1381                         my $host_name = $hit->{hostname};
1382                         my $host_key = $hit->{hostkey};
1383                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1384                         &update_jobdb_status_for_send_msgs($answer, $error);
1385                     }
1386                 }
1388                 # targets of msg are all gosa-si-server in known_server_db
1389                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1390                     # answer is for all server in known_server
1391                     my $sql_statement= "SELECT * FROM $known_server_tn";
1392                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1393                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1394                         my $host_name = $hit->{hostname};
1395                         my $host_key = $hit->{hostkey};
1396                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1397                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1398                         &update_jobdb_status_for_send_msgs($answer, $error);
1399                     }
1400                 }
1402                 # target of msg is GOsa
1403                                 elsif( $answer_target eq "GOSA" ) {
1404                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1405                                         my $add_on = "";
1406                     if( defined $session_id ) {
1407                         $add_on = ".session_id=$session_id";
1408                     }
1409                     # answer is for GOSA and has to returned to connected client
1410                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1411                     $client_answer = $gosa_answer.$add_on;
1412                 }
1414                 # target of msg is job queue at this host
1415                 elsif( $answer_target eq "JOBDB") {
1416                     $answer =~ /<header>(\S+)<\/header>/;   
1417                     my $header;
1418                     if( defined $1 ) { $header = $1; }
1419                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1420                     &update_jobdb_status_for_send_msgs($answer, $error);
1421                 }
1423                 # Target of msg is a mac address
1424                 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 ) {
1425                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1426                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1427                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1428                     my $found_ip_flag = 0;
1429                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1430                         my $host_name = $hit->{hostname};
1431                         my $host_key = $hit->{hostkey};
1432                         $answer =~ s/$answer_target/$host_name/g;
1433                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1434                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1435                         &update_jobdb_status_for_send_msgs($answer, $error);
1436                         $found_ip_flag++ ;
1437                     }   
1438                     if ($found_ip_flag == 0) {
1439                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1440                         my $res = $foreign_clients_db->select_dbentry($sql);
1441                         while( my ($hit_num, $hit) = each %{ $res } ) {
1442                             my $host_name = $hit->{hostname};
1443                             my $reg_server = $hit->{regserver};
1444                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1445                             
1446                             # Fetch key for reg_server
1447                             my $reg_server_key;
1448                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1449                             my $res = $known_server_db->select_dbentry($sql);
1450                             if (exists $res->{1}) {
1451                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1452                             } else {
1453                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1454                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1455                                 $reg_server_key = undef;
1456                             }
1458                             # Send answer to server where client is registered
1459                             if (defined $reg_server_key) {
1460                                 $answer =~ s/$answer_target/$host_name/g;
1461                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1462                                 &update_jobdb_status_for_send_msgs($answer, $error);
1463                                 $found_ip_flag++ ;
1464                             }
1465                         }
1466                     }
1467                     if( $found_ip_flag == 0) {
1468                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1469                     }
1471                 # Answer is for one specific host   
1472                 } else {
1473                     # get encrypt_key
1474                     my $encrypt_key = &get_encrypt_key($answer_target);
1475                     if( not defined $encrypt_key ) {
1476                         # unknown target
1477                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1478                         next;
1479                     }
1480                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1481                     &update_jobdb_status_for_send_msgs($answer, $error);
1482                 }
1483             }
1484         }
1485     }
1487     my $filter = POE::Filter::Reference->new();
1488     my %result = ( 
1489             status => "seems ok to me",
1490             answer => $client_answer,
1491             );
1493     my $output = $filter->put( [ \%result ] );
1494     print @$output;
1499 sub session_start {
1500     my ($kernel) = $_[KERNEL];
1501     $global_kernel = $kernel;
1502     $kernel->yield('register_at_foreign_servers');
1503         $kernel->yield('create_fai_server_db', $fai_server_tn );
1504         $kernel->yield('create_fai_release_db', $fai_release_tn );
1505     $kernel->yield('watch_for_next_tasks');
1506         $kernel->sig(USR1 => "sig_handler");
1507         $kernel->sig(USR2 => "recreate_packages_db");
1508         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1509         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1510     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1511         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1512     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1513         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1514     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1516     # Start opsi check
1517     if ($opsi_enabled eq "true") {
1518         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1519     }
1524 sub watch_for_done_jobs {
1525     #CHECK: $heap for what?
1526     my ($kernel,$heap) = @_[KERNEL, HEAP];
1528     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1529         my $res = $job_db->select_dbentry( $sql_statement );
1531     while( my ($id, $hit) = each %{$res} ) {
1532         my $jobdb_id = $hit->{id};
1533         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1534         my $res = $job_db->del_dbentry($sql_statement); 
1535     }
1537     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1541 sub watch_for_opsi_jobs {
1542     my ($kernel) = $_[KERNEL];
1544     # 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 
1545     # opsi install job is to parse the xml message. There is still the correct header.
1546     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1547         my $res = $job_db->select_dbentry( $sql_statement );
1549     # Ask OPSI for an update of the running jobs
1550     while (my ($id, $hit) = each %$res ) {
1551         # Determine current parameters of the job
1552         my $hostId = $hit->{'plainname'};
1553         my $macaddress = $hit->{'macaddress'};
1554         my $progress = $hit->{'progress'};
1556         my $result= {};
1557         
1558         # For hosts, only return the products that are or get installed
1559         my $callobj;
1560         $callobj = {
1561             method  => 'getProductStates_hash',
1562             params  => [ $hostId ],
1563             id  => 1,
1564         };
1565         
1566         my $hres = $opsi_client->call($opsi_url, $callobj);
1567         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1568         if (not &check_opsi_res($hres)) {
1569             my $htmp= $hres->result->{$hostId};
1570         
1571             # Check state != not_installed or action == setup -> load and add
1572             my $products= 0;
1573             my $installed= 0;
1574             my $installing = 0;
1575             my $error= 0;  
1576             my @installed_list;
1577             my @error_list;
1578             my $act_status = "none";
1579             foreach my $product (@{$htmp}){
1581                 if ($product->{'installationStatus'} ne "not_installed" or
1582                         $product->{'actionRequest'} eq "setup"){
1584                     # Increase number of products for this host
1585                     $products++;
1586         
1587                     if ($product->{'installationStatus'} eq "failed"){
1588                         $result->{$product->{'productId'}}= "error";
1589                         unshift(@error_list, $product->{'productId'});
1590                         $error++;
1591                     }
1592                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1593                         $result->{$product->{'productId'}}= "installed";
1594                         unshift(@installed_list, $product->{'productId'});
1595                         $installed++;
1596                     }
1597                     if ($product->{'installationStatus'} eq "installing"){
1598                         $result->{$product->{'productId'}}= "installing";
1599                         $installing++;
1600                         $act_status = "installing - ".$product->{'productId'};
1601                     }
1602                 }
1603             }
1604         
1605             # Estimate "rough" progress, avoid division by zero
1606             if ($products == 0) {
1607                 $result->{'progress'}= 0;
1608             } else {
1609                 $result->{'progress'}= int($installed * 100 / $products);
1610             }
1612             # Set updates in job queue
1613             if ((not $error) && (not $installing) && ($installed)) {
1614                 $act_status = "installed - ".join(", ", @installed_list);
1615             }
1616             if ($error) {
1617                 $act_status = "error - ".join(", ", @error_list);
1618             }
1619             if ($progress ne $result->{'progress'} ) {
1620                 # Updating progress and result 
1621                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1622                 my $update_res = $job_db->update_dbentry($update_statement);
1623             }
1624             if ($progress eq 100) { 
1625                 # Updateing status
1626                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1627                 if ($error) {
1628                     $done_statement .= "status='error'";
1629                 } else {
1630                     $done_statement .= "status='done'";
1631                 }
1632                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1633                 my $done_res = $job_db->update_dbentry($done_statement);
1634             }
1637         }
1638     }
1640     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1644 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1645 sub watch_for_modified_jobs {
1646     my ($kernel,$heap) = @_[KERNEL, HEAP];
1648     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1649     my $res = $job_db->select_dbentry( $sql_statement );
1650     
1651     # if db contains no jobs which should be update, do nothing
1652     if (keys %$res != 0) {
1654         if ($job_synchronization  eq "true") {
1655             # make out of the db result a gosa-si message   
1656             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1657  
1658             # update all other SI-server
1659             &inform_all_other_si_server($update_msg);
1660         }
1662         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1663         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1664         $res = $job_db->update_dbentry($sql_statement);
1665     }
1667     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1671 sub watch_for_new_jobs {
1672         if($watch_for_new_jobs_in_progress == 0) {
1673                 $watch_for_new_jobs_in_progress = 1;
1674                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1676                 # check gosa job quaeue for jobs with executable timestamp
1677                 my $timestamp = &get_time();
1678                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1679                 my $res = $job_db->exec_statement( $sql_statement );
1681                 # Merge all new jobs that would do the same actions
1682                 my @drops;
1683                 my $hits;
1684                 foreach my $hit (reverse @{$res} ) {
1685                         my $macaddress= lc @{$hit}[8];
1686                         my $headertag= @{$hit}[5];
1687                         if(
1688                                 defined($hits->{$macaddress}) &&
1689                                 defined($hits->{$macaddress}->{$headertag}) &&
1690                                 defined($hits->{$macaddress}->{$headertag}[0])
1691                         ) {
1692                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1693                         }
1694                         $hits->{$macaddress}->{$headertag}= $hit;
1695                 }
1697                 # Delete new jobs with a matching job in state 'processing'
1698                 foreach my $macaddress (keys %{$hits}) {
1699                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1700                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1701                                 if(defined($jobdb_id)) {
1702                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1703                                         my $res = $job_db->exec_statement( $sql_statement );
1704                                         foreach my $hit (@{$res}) {
1705                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1706                                         }
1707                                 } else {
1708                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1709                                 }
1710                         }
1711                 }
1713                 # Commit deletion
1714                 $job_db->exec_statementlist(\@drops);
1716                 # Look for new jobs that could be executed
1717                 foreach my $macaddress (keys %{$hits}) {
1719                         # Look if there is an executing job
1720                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1721                         my $res = $job_db->exec_statement( $sql_statement );
1723                         # Skip new jobs for host if there is a processing job
1724                         if(defined($res) and defined @{$res}[0]) {
1725                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1726                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1727                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1728                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1729                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1730                                         if(defined($res_2) and defined @{$res_2}[0]) {
1731                                                 # Set status from goto-activation to 'waiting' and update timestamp
1732                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1733                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1734                                         }
1735                                 }
1736                                 next;
1737                         }
1739                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1740                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1741                                 if(defined($jobdb_id)) {
1742                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1744                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1745                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1746                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1748                                         # expect macaddress is unique!!!!!!
1749                                         my $target = $res_hash->{1}->{hostname};
1751                                         # change header
1752                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1754                                         # add sqlite_id
1755                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1757                                         $job_msg =~ /<header>(\S+)<\/header>/;
1758                                         my $header = $1 ;
1759                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1761                                         # update status in job queue to 'processing'
1762                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1763                                         my $res = $job_db->update_dbentry($sql_statement);
1764 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1766                                         # We don't want parallel processing
1767                                         last;
1768                                 }
1769                         }
1770                 }
1772                 $watch_for_new_jobs_in_progress = 0;
1773                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1774         }
1778 sub watch_for_new_messages {
1779     my ($kernel,$heap) = @_[KERNEL, HEAP];
1780     my @coll_user_msg;   # collection list of outgoing messages
1781     
1782     # check messaging_db for new incoming messages with executable timestamp
1783     my $timestamp = &get_time();
1784     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1785     my $res = $messaging_db->exec_statement( $sql_statement );
1786         foreach my $hit (@{$res}) {
1788         # create outgoing messages
1789         my $message_to = @{$hit}[3];
1790         # translate message_to to plain login name
1791         my @message_to_l = split(/,/, $message_to);  
1792                 my %receiver_h; 
1793                 foreach my $receiver (@message_to_l) {
1794                         if ($receiver =~ /^u_([\s\S]*)$/) {
1795                                 $receiver_h{$1} = 0;
1796                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1797                                 my $group_name = $1;
1798                                 # fetch all group members from ldap and add them to receiver hash
1799                                 my $ldap_handle = &get_ldap_handle();
1800                                 if (defined $ldap_handle) {
1801                                                 my $mesg = $ldap_handle->search(
1802                                                                                 base => $ldap_base,
1803                                                                                 scope => 'sub',
1804                                                                                 attrs => ['memberUid'],
1805                                                                                 filter => "cn=$group_name",
1806                                                                                 );
1807                                                 if ($mesg->count) {
1808                                                                 my @entries = $mesg->entries;
1809                                                                 foreach my $entry (@entries) {
1810                                                                                 my @receivers= $entry->get_value("memberUid");
1811                                                                                 foreach my $receiver (@receivers) { 
1812                                                                                                 $receiver_h{$1} = 0;
1813                                                                                 }
1814                                                                 }
1815                                                 } 
1816                                                 # translating errors ?
1817                                                 if ($mesg->code) {
1818                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1819                                                 }
1820                                 # ldap handle error ?           
1821                                 } else {
1822                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1823                                 }
1824                         } else {
1825                                 my $sbjct = &encode_base64(@{$hit}[1]);
1826                                 my $msg = &encode_base64(@{$hit}[7]);
1827                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1828                         }
1829                 }
1830                 my @receiver_l = keys(%receiver_h);
1832         my $message_id = @{$hit}[0];
1834         #add each outgoing msg to messaging_db
1835         my $receiver;
1836         foreach $receiver (@receiver_l) {
1837             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1838                 "VALUES ('".
1839                 $message_id."', '".    # id
1840                 @{$hit}[1]."', '".     # subject
1841                 @{$hit}[2]."', '".     # message_from
1842                 $receiver."', '".      # message_to
1843                 "none"."', '".         # flag
1844                 "out"."', '".          # direction
1845                 @{$hit}[6]."', '".     # delivery_time
1846                 @{$hit}[7]."', '".     # message
1847                 $timestamp."'".     # timestamp
1848                 ")";
1849             &daemon_log("M DEBUG: $sql_statement", 1);
1850             my $res = $messaging_db->exec_statement($sql_statement);
1851             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1852         }
1854         # set incoming message to flag d=deliverd
1855         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1856         &daemon_log("M DEBUG: $sql_statement", 7);
1857         $res = $messaging_db->update_dbentry($sql_statement);
1858         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1859     }
1861     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1862     return;
1865 sub watch_for_delivery_messages {
1866     my ($kernel, $heap) = @_[KERNEL, HEAP];
1868     # select outgoing messages
1869     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1870     #&daemon_log("0 DEBUG: $sql", 7);
1871     my $res = $messaging_db->exec_statement( $sql_statement );
1872     
1873     # build out msg for each    usr
1874     foreach my $hit (@{$res}) {
1875         my $receiver = @{$hit}[3];
1876         my $msg_id = @{$hit}[0];
1877         my $subject = @{$hit}[1];
1878         my $message = @{$hit}[7];
1880         # resolve usr -> host where usr is logged in
1881         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1882         #&daemon_log("0 DEBUG: $sql", 7);
1883         my $res = $login_users_db->exec_statement($sql);
1885         # reciver is logged in nowhere
1886         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1888                 my $send_succeed = 0;
1889                 foreach my $hit (@$res) {
1890                                 my $receiver_host = @$hit[0];
1891                 my $delivered2host = 0;
1892                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1894                                 # Looking for host in know_clients_db 
1895                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1896                                 my $res = $known_clients_db->exec_statement($sql);
1898                 # Host is known in known_clients_db
1899                 if (ref(@$res[0]) eq "ARRAY") {
1900                     my $receiver_key = @{@{$res}[0]}[2];
1901                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1902                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1903                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1904                     if ($error == 0 ) {
1905                         $send_succeed++ ;
1906                         $delivered2host++ ;
1907                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1908                     } else {
1909                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1910                     }
1911                 }
1912                 
1913                 # Message already send, do not need to do anything more, otherwise ...
1914                 if ($delivered2host) { next;}
1915     
1916                 # ...looking for host in foreign_clients_db
1917                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1918                 $res = $foreign_clients_db->exec_statement($sql);
1919   
1920                                 # Host is known in foreign_clients_db 
1921                                 if (ref(@$res[0]) eq "ARRAY") { 
1922                     my $registration_server = @{@{$res}[0]}[2];
1923                     
1924                     # Fetch encryption key for registration server
1925                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1926                     my $res = $known_server_db->exec_statement($sql);
1927                     if (ref(@$res[0]) eq "ARRAY") { 
1928                         my $registration_server_key = @{@{$res}[0]}[3];
1929                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1930                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1931                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1932                         if ($error == 0 ) {
1933                             $send_succeed++ ;
1934                             $delivered2host++ ;
1935                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1936                         } else {
1937                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1938                         }
1940                     } else {
1941                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1942                                 "registrated at server '$registration_server', ".
1943                                 "but no data available in known_server_db ", 1); 
1944                     }
1945                 }
1946                 
1947                 if (not $delivered2host) {
1948                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1949                 }
1950                 }
1952                 if ($send_succeed) {
1953                                 # set outgoing msg at db to deliverd
1954                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1955                                 my $res = $messaging_db->exec_statement($sql); 
1956                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1957                 } else {
1958             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1959         }
1960         }
1962     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1963     return;
1967 sub watch_for_done_messages {
1968     my ($kernel,$heap) = @_[KERNEL, HEAP];
1970     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1971     #&daemon_log("0 DEBUG: $sql", 7);
1972     my $res = $messaging_db->exec_statement($sql); 
1974     foreach my $hit (@{$res}) {
1975         my $msg_id = @{$hit}[0];
1977         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1978         #&daemon_log("0 DEBUG: $sql", 7); 
1979         my $res = $messaging_db->exec_statement($sql);
1981         # not all usr msgs have been seen till now
1982         if ( ref(@$res[0]) eq "ARRAY") { next; }
1983         
1984         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1985         #&daemon_log("0 DEBUG: $sql", 7);
1986         $res = $messaging_db->exec_statement($sql);
1987     
1988     }
1990     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1991     return;
1995 sub watch_for_old_known_clients {
1996     my ($kernel,$heap) = @_[KERNEL, HEAP];
1998     my $sql_statement = "SELECT * FROM $known_clients_tn";
1999     my $res = $known_clients_db->select_dbentry( $sql_statement );
2001     my $act_time = int(&get_time());
2003     while ( my ($hit_num, $hit) = each %$res) {
2004         my $expired_timestamp = int($hit->{'timestamp'});
2005         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2006         my $dt = DateTime->new( year   => $1,
2007                 month  => $2,
2008                 day    => $3,
2009                 hour   => $4,
2010                 minute => $5,
2011                 second => $6,
2012                 );
2014         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2015         $expired_timestamp = $dt->ymd('').$dt->hms('');
2016         if ($act_time > $expired_timestamp) {
2017             my $hostname = $hit->{'hostname'};
2018             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2019             my $del_res = $known_clients_db->exec_statement($del_sql);
2021             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2022         }
2024     }
2026     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2030 sub watch_for_next_tasks {
2031     my ($kernel,$heap) = @_[KERNEL, HEAP];
2033     my $sql = "SELECT * FROM $incoming_tn";
2034     my $res = $incoming_db->select_dbentry($sql);
2036     while ( my ($hit_num, $hit) = each %$res) {
2037         my $headertag = $hit->{'headertag'};
2038         if ($headertag =~ /^answer_(\d+)/) {
2039             # do not start processing, this message is for a still running POE::Wheel
2040             next;
2041         }
2042         my $message_id = $hit->{'id'};
2043         $kernel->yield('next_task', $hit);
2045         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2046         my $res = $incoming_db->exec_statement($sql);
2047     }
2049     $kernel->delay_set('watch_for_next_tasks', 0.1); 
2053 sub get_ldap_handle {
2054         my ($session_id) = @_;
2055         my $heap;
2056         my $ldap_handle;
2058         if (not defined $session_id ) { $session_id = 0 };
2059         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2061         if ($session_id == 0) {
2062                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2063                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2064                 $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!"); 
2066         } else {
2067                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2068                 if( defined $session_reference ) {
2069                         $heap = $session_reference->get_heap();
2070                 }
2072                 if (not defined $heap) {
2073                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2074                         return;
2075                 }
2077                 # TODO: This "if" is nonsense, because it doesn't prove that the
2078                 #       used handle is still valid - or if we've to reconnect...
2079                 #if (not exists $heap->{ldap_handle}) {
2080                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2081                         $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!"); 
2082                         $heap->{ldap_handle} = $ldap_handle;
2083                 #}
2084         }
2085         return $ldap_handle;
2089 sub change_fai_state {
2090     my ($st, $targets, $session_id) = @_;
2091     $session_id = 0 if not defined $session_id;
2092     # Set FAI state to localboot
2093     my %mapActions= (
2094         reboot    => '',
2095         update    => 'softupdate',
2096         localboot => 'localboot',
2097         reinstall => 'install',
2098         rescan    => '',
2099         wake      => '',
2100         memcheck  => 'memcheck',
2101         sysinfo   => 'sysinfo',
2102         install   => 'install',
2103     );
2105     # Return if this is unknown
2106     if (!exists $mapActions{ $st }){
2107         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2108       return;
2109     }
2111     my $state= $mapActions{ $st };
2113     my $ldap_handle = &get_ldap_handle($session_id);
2114     if( defined($ldap_handle) ) {
2116       # Build search filter for hosts
2117         my $search= "(&(objectClass=GOhard)";
2118         foreach (@{$targets}){
2119             $search.= "(macAddress=$_)";
2120         }
2121         $search.= ")";
2123       # If there's any host inside of the search string, procress them
2124         if (!($search =~ /macAddress/)){
2125             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2126             return;
2127         }
2129       # Perform search for Unit Tag
2130       my $mesg = $ldap_handle->search(
2131           base   => $ldap_base,
2132           scope  => 'sub',
2133           attrs  => ['dn', 'FAIstate', 'objectClass'],
2134           filter => "$search"
2135           );
2137           if ($mesg->count) {
2138                   my @entries = $mesg->entries;
2139                   if (0 == @entries) {
2140                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2141                   }
2143                   foreach my $entry (@entries) {
2144                           # Only modify entry if it is not set to '$state'
2145                           if ($entry->get_value("FAIstate") ne "$state"){
2146                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2147                                   my $result;
2148                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2149                                   if (exists $tmp{'FAIobject'}){
2150                                           if ($state eq ''){
2151                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2152                                                           delete => [ FAIstate => [] ] ]);
2153                                           } else {
2154                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2155                                                           replace => [ FAIstate => $state ] ]);
2156                                           }
2157                                   } elsif ($state ne ''){
2158                                           $result= $ldap_handle->modify($entry->dn, changes => [
2159                                                   add     => [ objectClass => 'FAIobject' ],
2160                                                   add     => [ FAIstate => $state ] ]);
2161                                   }
2163                                   # Errors?
2164                                   if ($result->code){
2165                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2166                                   }
2167                           } else {
2168                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2169                           }  
2170                   }
2171           } else {
2172                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2173           }
2175     # if no ldap handle defined
2176     } else {
2177         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2178     }
2180         return;
2184 sub change_goto_state {
2185     my ($st, $targets, $session_id) = @_;
2186     $session_id = 0  if not defined $session_id;
2188     # Switch on or off?
2189     my $state= $st eq 'active' ? 'active': 'locked';
2191     my $ldap_handle = &get_ldap_handle($session_id);
2192     if( defined($ldap_handle) ) {
2194       # Build search filter for hosts
2195       my $search= "(&(objectClass=GOhard)";
2196       foreach (@{$targets}){
2197         $search.= "(macAddress=$_)";
2198       }
2199       $search.= ")";
2201       # If there's any host inside of the search string, procress them
2202       if (!($search =~ /macAddress/)){
2203         return;
2204       }
2206       # Perform search for Unit Tag
2207       my $mesg = $ldap_handle->search(
2208           base   => $ldap_base,
2209           scope  => 'sub',
2210           attrs  => ['dn', 'gotoMode'],
2211           filter => "$search"
2212           );
2214       if ($mesg->count) {
2215         my @entries = $mesg->entries;
2216         foreach my $entry (@entries) {
2218           # Only modify entry if it is not set to '$state'
2219           if ($entry->get_value("gotoMode") ne $state){
2221             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2222             my $result;
2223             $result= $ldap_handle->modify($entry->dn, changes => [
2224                                                 replace => [ gotoMode => $state ] ]);
2226             # Errors?
2227             if ($result->code){
2228               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2229             }
2231           }
2232         }
2233       } else {
2234                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2235           }
2237     }
2241 sub run_recreate_packages_db {
2242     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2243     my $session_id = $session->ID;
2244         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2245         $kernel->yield('create_fai_release_db', $fai_release_tn);
2246         $kernel->yield('create_fai_server_db', $fai_server_tn);
2247         return;
2251 sub run_create_fai_server_db {
2252     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2253     my $session_id = $session->ID;
2254     my $task = POE::Wheel::Run->new(
2255             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2256             StdoutEvent  => "session_run_result",
2257             StderrEvent  => "session_run_debug",
2258             CloseEvent   => "session_run_done",
2259             );
2261     $heap->{task}->{ $task->ID } = $task;
2262     return;
2266 sub create_fai_server_db {
2267     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2268         my $result;
2270         if (not defined $session_id) { $session_id = 0; }
2271     my $ldap_handle = &get_ldap_handle();
2272         if(defined($ldap_handle)) {
2273                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2274                 my $mesg= $ldap_handle->search(
2275                         base   => $ldap_base,
2276                         scope  => 'sub',
2277                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2278                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2279                 );
2280                 if($mesg->{'resultCode'} == 0 &&
2281                    $mesg->count != 0) {
2282                    foreach my $entry (@{$mesg->{entries}}) {
2283                            if($entry->exists('FAIrepository')) {
2284                                    # Add an entry for each Repository configured for server
2285                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2286                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2287                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2288                                                    $result= $fai_server_db->add_dbentry( { 
2289                                                                    table => $table_name,
2290                                                                    primkey => ['server', 'release', 'tag'],
2291                                                                    server => $tmp_url,
2292                                                                    release => $tmp_release,
2293                                                                    sections => $tmp_sections,
2294                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2295                                                            } );
2296                                            }
2297                                    }
2298                            }
2299                    }
2300                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2302                 # TODO: Find a way to post the 'create_packages_list_db' event
2303                 if(not defined($dont_create_packages_list)) {
2304                         &create_packages_list_db(undef, undef, $session_id);
2305                 }
2306         }       
2307     
2308     $ldap_handle->disconnect;
2309         return $result;
2313 sub run_create_fai_release_db {
2314     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2315         my $session_id = $session->ID;
2316     my $task = POE::Wheel::Run->new(
2317             Program => sub { &create_fai_release_db($table_name, $session_id) },
2318             StdoutEvent  => "session_run_result",
2319             StderrEvent  => "session_run_debug",
2320             CloseEvent   => "session_run_done",
2321             );
2323     $heap->{task}->{ $task->ID } = $task;
2324     return;
2328 sub create_fai_release_db {
2329         my ($table_name, $session_id) = @_;
2330         my $result;
2332     # used for logging
2333     if (not defined $session_id) { $session_id = 0; }
2335     my $ldap_handle = &get_ldap_handle();
2336         if(defined($ldap_handle)) {
2337                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2338                 my $mesg= $ldap_handle->search(
2339                         base   => $ldap_base,
2340                         scope  => 'sub',
2341                         attrs  => [],
2342                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2343                 );
2344                 if($mesg->{'resultCode'} == 0 &&
2345                         $mesg->count != 0) {
2346                         # Walk through all possible FAI container ou's
2347                         my @sql_list;
2348                         my $timestamp= &get_time();
2349                         foreach my $ou (@{$mesg->{entries}}) {
2350                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2351                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2352                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2353                                         if(@tmp_array) {
2354                                                 foreach my $entry (@tmp_array) {
2355                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2356                                                                 my $sql= 
2357                                                                 "INSERT INTO $table_name "
2358                                                                 ."(timestamp, release, class, type, state) VALUES ("
2359                                                                 .$timestamp.","
2360                                                                 ."'".$entry->{'release'}."',"
2361                                                                 ."'".$entry->{'class'}."',"
2362                                                                 ."'".$entry->{'type'}."',"
2363                                                                 ."'".$entry->{'state'}."')";
2364                                                                 push @sql_list, $sql;
2365                                                         }
2366                                                 }
2367                                         }
2368                                 }
2369                         }
2371                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2372                         if(@sql_list) {
2373                                 unshift @sql_list, "VACUUM";
2374                                 unshift @sql_list, "DELETE FROM $table_name";
2375                                 $fai_release_db->exec_statementlist(\@sql_list);
2376                         }
2377                         daemon_log("$session_id DEBUG: Done with inserting",7);
2378                 }
2379                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2380         }
2381     $ldap_handle->disconnect;
2382         return $result;
2385 sub get_fai_types {
2386         my $tmp_classes = shift || return undef;
2387         my @result;
2389         foreach my $type(keys %{$tmp_classes}) {
2390                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2391                         my $entry = {
2392                                 type => $type,
2393                                 state => $tmp_classes->{$type}[0],
2394                         };
2395                         push @result, $entry;
2396                 }
2397         }
2399         return @result;
2402 sub get_fai_state {
2403         my $result = "";
2404         my $tmp_classes = shift || return $result;
2406         foreach my $type(keys %{$tmp_classes}) {
2407                 if(defined($tmp_classes->{$type}[0])) {
2408                         $result = $tmp_classes->{$type}[0];
2409                         
2410                 # State is equal for all types in class
2411                         last;
2412                 }
2413         }
2415         return $result;
2418 sub resolve_fai_classes {
2419         my ($fai_base, $ldap_handle, $session_id) = @_;
2420         if (not defined $session_id) { $session_id = 0; }
2421         my $result;
2422         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2423         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2424         my $fai_classes;
2426         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2427         my $mesg= $ldap_handle->search(
2428                 base   => $fai_base,
2429                 scope  => 'sub',
2430                 attrs  => ['cn','objectClass','FAIstate'],
2431                 filter => $fai_filter,
2432         );
2433         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2435         if($mesg->{'resultCode'} == 0 &&
2436                 $mesg->count != 0) {
2437                 foreach my $entry (@{$mesg->{entries}}) {
2438                         if($entry->exists('cn')) {
2439                                 my $tmp_dn= $entry->dn();
2441                                 # Skip classname and ou dn parts for class
2442                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2444                                 # Skip classes without releases
2445                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2446                                         next;
2447                                 }
2449                                 my $tmp_cn= $entry->get_value('cn');
2450                                 my $tmp_state= $entry->get_value('FAIstate');
2452                                 my $tmp_type;
2453                                 # Get FAI type
2454                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2455                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2456                                                 $tmp_type= $oclass;
2457                                                 last;
2458                                         }
2459                                 }
2461                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2462                                         # A Subrelease
2463                                         my @sub_releases = split(/,/, $tmp_release);
2465                                         # Walk through subreleases and build hash tree
2466                                         my $hash;
2467                                         while(my $tmp_sub_release = pop @sub_releases) {
2468                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2469                                         }
2470                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2471                                 } else {
2472                                         # A branch, no subrelease
2473                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2474                                 }
2475                         } elsif (!$entry->exists('cn')) {
2476                                 my $tmp_dn= $entry->dn();
2477                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2479                                 # Skip classes without releases
2480                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2481                                         next;
2482                                 }
2484                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2485                                         # A Subrelease
2486                                         my @sub_releases= split(/,/, $tmp_release);
2488                                         # Walk through subreleases and build hash tree
2489                                         my $hash;
2490                                         while(my $tmp_sub_release = pop @sub_releases) {
2491                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2492                                         }
2493                                         # Remove the last two characters
2494                                         chop($hash);
2495                                         chop($hash);
2497                                         eval('$fai_classes->'.$hash.'= {}');
2498                                 } else {
2499                                         # A branch, no subrelease
2500                                         if(!exists($fai_classes->{$tmp_release})) {
2501                                                 $fai_classes->{$tmp_release} = {};
2502                                         }
2503                                 }
2504                         }
2505                 }
2507                 # The hash is complete, now we can honor the copy-on-write based missing entries
2508                 foreach my $release (keys %$fai_classes) {
2509                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2510                 }
2511         }
2512         return $result;
2515 sub apply_fai_inheritance {
2516        my $fai_classes = shift || return {};
2517        my $tmp_classes;
2519        # Get the classes from the branch
2520        foreach my $class (keys %{$fai_classes}) {
2521                # Skip subreleases
2522                if($class =~ /^ou=.*$/) {
2523                        next;
2524                } else {
2525                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2526                }
2527        }
2529        # Apply to each subrelease
2530        foreach my $subrelease (keys %{$fai_classes}) {
2531                if($subrelease =~ /ou=/) {
2532                        foreach my $tmp_class (keys %{$tmp_classes}) {
2533                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2534                                        $fai_classes->{$subrelease}->{$tmp_class} =
2535                                        deep_copy($tmp_classes->{$tmp_class});
2536                                } else {
2537                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2538                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2539                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2540                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2541                                                }
2542                                        }
2543                                }
2544                        }
2545                }
2546        }
2548        # Find subreleases in deeper levels
2549        foreach my $subrelease (keys %{$fai_classes}) {
2550                if($subrelease =~ /ou=/) {
2551                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2552                                if($subsubrelease =~ /ou=/) {
2553                                        apply_fai_inheritance($fai_classes->{$subrelease});
2554                                }
2555                        }
2556                }
2557        }
2559        return $fai_classes;
2562 sub get_fai_release_entries {
2563         my $tmp_classes = shift || return;
2564         my $parent = shift || "";
2565         my @result = shift || ();
2567         foreach my $entry (keys %{$tmp_classes}) {
2568                 if(defined($entry)) {
2569                         if($entry =~ /^ou=.*$/) {
2570                                 my $release_name = $entry;
2571                                 $release_name =~ s/ou=//g;
2572                                 if(length($parent)>0) {
2573                                         $release_name = $parent."/".$release_name;
2574                                 }
2575                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2576                                 foreach my $bufentry(@bufentries) {
2577                                         push @result, $bufentry;
2578                                 }
2579                         } else {
2580                                 my @types = get_fai_types($tmp_classes->{$entry});
2581                                 foreach my $type (@types) {
2582                                         push @result, 
2583                                         {
2584                                                 'class' => $entry,
2585                                                 'type' => $type->{'type'},
2586                                                 'release' => $parent,
2587                                                 'state' => $type->{'state'},
2588                                         };
2589                                 }
2590                         }
2591                 }
2592         }
2594         return @result;
2597 sub deep_copy {
2598         my $this = shift;
2599         if (not ref $this) {
2600                 $this;
2601         } elsif (ref $this eq "ARRAY") {
2602                 [map deep_copy($_), @$this];
2603         } elsif (ref $this eq "HASH") {
2604                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2605         } else { die "what type is $_?" }
2609 sub session_run_result {
2610     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2611     $kernel->sig(CHLD => "child_reap");
2614 sub session_run_debug {
2615     my $result = $_[ARG0];
2616     print STDERR "$result\n";
2619 sub session_run_done {
2620     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2621     delete $heap->{task}->{$task_id};
2625 sub create_sources_list {
2626         my $session_id = shift;
2627         my $ldap_handle = &main::get_ldap_handle;
2628         my $result="/tmp/gosa_si_tmp_sources_list";
2630         # Remove old file
2631         if(stat($result)) {
2632                 unlink($result);
2633                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2634         }
2636         my $fh;
2637         open($fh, ">$result");
2638         if (not defined $fh) {
2639                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2640                 return undef;
2641         }
2642         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2643                 my $mesg=$ldap_handle->search(
2644                         base    => $main::ldap_server_dn,
2645                         scope   => 'base',
2646                         attrs   => 'FAIrepository',
2647                         filter  => 'objectClass=FAIrepositoryServer'
2648                 );
2649                 if($mesg->count) {
2650                         foreach my $entry(@{$mesg->{'entries'}}) {
2651                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2652                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2653                                         my $line = "deb $server $release";
2654                                         $sections =~ s/,/ /g;
2655                                         $line.= " $sections";
2656                                         print $fh $line."\n";
2657                                 }
2658                         }
2659                 }
2660         } else {
2661                 if (defined $main::ldap_server_dn){
2662                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2663                 } else {
2664                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2665                 }
2666         }
2667         close($fh);
2669         return $result;
2673 sub run_create_packages_list_db {
2674     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2675         my $session_id = $session->ID;
2677         my $task = POE::Wheel::Run->new(
2678                                         Priority => +20,
2679                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2680                                         StdoutEvent  => "session_run_result",
2681                                         StderrEvent  => "session_run_debug",
2682                                         CloseEvent   => "session_run_done",
2683                                         );
2684         $heap->{task}->{ $task->ID } = $task;
2688 sub create_packages_list_db {
2689         my ($ldap_handle, $sources_file, $session_id) = @_;
2690         
2691         # it should not be possible to trigger a recreation of packages_list_db
2692         # while packages_list_db is under construction, so set flag packages_list_under_construction
2693         # which is tested befor recreation can be started
2694         if (-r $packages_list_under_construction) {
2695                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2696                 return;
2697         } else {
2698                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2699                 # set packages_list_under_construction to true
2700                 system("touch $packages_list_under_construction");
2701                 @packages_list_statements=();
2702         }
2704         if (not defined $session_id) { $session_id = 0; }
2705         if (not defined $ldap_handle) { 
2706                 $ldap_handle= &get_ldap_handle();
2708                 if (not defined $ldap_handle) {
2709                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2710                         unlink($packages_list_under_construction);
2711                         return;
2712                 }
2713         }
2714         if (not defined $sources_file) { 
2715                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2716                 $sources_file = &create_sources_list($session_id);
2717         }
2719         if (not defined $sources_file) {
2720                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2721                 unlink($packages_list_under_construction);
2722                 return;
2723         }
2725         my $line;
2727         open(CONFIG, "<$sources_file") or do {
2728                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2729                 unlink($packages_list_under_construction);
2730                 return;
2731         };
2733         # Read lines
2734         while ($line = <CONFIG>){
2735                 # Unify
2736                 chop($line);
2737                 $line =~ s/^\s+//;
2738                 $line =~ s/^\s+/ /;
2740                 # Strip comments
2741                 $line =~ s/#.*$//g;
2743                 # Skip empty lines
2744                 if ($line =~ /^\s*$/){
2745                         next;
2746                 }
2748                 # Interpret deb line
2749                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2750                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2751                         my $section;
2752                         foreach $section (split(' ', $sections)){
2753                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2754                         }
2755                 }
2756         }
2758         close (CONFIG);
2761         find(\&cleanup_and_extract, keys( %repo_dirs ));
2762         &main::strip_packages_list_statements();
2763         unshift @packages_list_statements, "VACUUM";
2764         $packages_list_db->exec_statementlist(\@packages_list_statements);
2765         unlink($packages_list_under_construction);
2766         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2767         return;
2770 # This function should do some intensive task to minimize the db-traffic
2771 sub strip_packages_list_statements {
2772     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2773         my @new_statement_list=();
2774         my $hash;
2775         my $insert_hash;
2776         my $update_hash;
2777         my $delete_hash;
2778         my $local_timestamp=get_time();
2780         foreach my $existing_entry (@existing_entries) {
2781                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2782         }
2784         foreach my $statement (@packages_list_statements) {
2785                 if($statement =~ /^INSERT/i) {
2786                         # Assign the values from the insert statement
2787                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2788                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2789                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2790                                 # If section or description has changed, update the DB
2791                                 if( 
2792                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2793                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2794                                 ) {
2795                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2796                                 }
2797                         } else {
2798                                 # Insert a non-existing entry to db
2799                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2800                         }
2801                 } elsif ($statement =~ /^UPDATE/i) {
2802                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2803                         /^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;
2804                         foreach my $distribution (keys %{$hash}) {
2805                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2806                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2807                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2808                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2809                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2810                                                 my $section;
2811                                                 my $description;
2812                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2813                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2814                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2815                                                 }
2816                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2817                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2818                                                 }
2819                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2820                                         }
2821                                 }
2822                         }
2823                 }
2824         }
2826         # TODO: Check for orphaned entries
2828         # unroll the insert_hash
2829         foreach my $distribution (keys %{$insert_hash}) {
2830                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2831                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2832                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2833                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2834                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2835                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2836                                 ."'$local_timestamp')";
2837                         }
2838                 }
2839         }
2841         # unroll the update hash
2842         foreach my $distribution (keys %{$update_hash}) {
2843                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2844                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2845                                 my $set = "";
2846                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2847                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2848                                 }
2849                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2850                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2851                                 }
2852                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2853                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2854                                 }
2855                                 if(defined($set) and length($set) > 0) {
2856                                         $set .= "timestamp = '$local_timestamp'";
2857                                 } else {
2858                                         next;
2859                                 }
2860                                 push @new_statement_list, 
2861                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2862                                         ." distribution = '$distribution'"
2863                                         ." AND package = '$package'"
2864                                         ." AND version = '$version'";
2865                         }
2866                 }
2867         }
2869         @packages_list_statements = @new_statement_list;
2873 sub parse_package_info {
2874     my ($baseurl, $dist, $section, $session_id)= @_;
2875     my ($package);
2876     if (not defined $session_id) { $session_id = 0; }
2877     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2878     $repo_dirs{ "${repo_path}/pool" } = 1;
2880     foreach $package ("Packages.gz"){
2881         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2882         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2883         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2884     }
2885     
2889 sub get_package {
2890     my ($url, $dest, $session_id)= @_;
2891     if (not defined $session_id) { $session_id = 0; }
2893     my $tpath = dirname($dest);
2894     -d "$tpath" || mkpath "$tpath";
2896     # This is ugly, but I've no time to take a look at "how it works in perl"
2897     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2898         system("gunzip -cd '$dest' > '$dest.in'");
2899         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2900         unlink($dest);
2901         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2902     } else {
2903         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2904     }
2905     return 0;
2909 sub parse_package {
2910     my ($path, $dist, $srv_path, $session_id)= @_;
2911     if (not defined $session_id) { $session_id = 0;}
2912     my ($package, $version, $section, $description);
2913     my $PACKAGES;
2914     my $timestamp = &get_time();
2916     if(not stat("$path.in")) {
2917         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2918         return;
2919     }
2921     open($PACKAGES, "<$path.in");
2922     if(not defined($PACKAGES)) {
2923         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2924         return;
2925     }
2927     # Read lines
2928     while (<$PACKAGES>){
2929         my $line = $_;
2930         # Unify
2931         chop($line);
2933         # Use empty lines as a trigger
2934         if ($line =~ /^\s*$/){
2935             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2936             push(@packages_list_statements, $sql);
2937             $package = "none";
2938             $version = "none";
2939             $section = "none";
2940             $description = "none"; 
2941             next;
2942         }
2944         # Trigger for package name
2945         if ($line =~ /^Package:\s/){
2946             ($package)= ($line =~ /^Package: (.*)$/);
2947             next;
2948         }
2950         # Trigger for version
2951         if ($line =~ /^Version:\s/){
2952             ($version)= ($line =~ /^Version: (.*)$/);
2953             next;
2954         }
2956         # Trigger for description
2957         if ($line =~ /^Description:\s/){
2958             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2959             next;
2960         }
2962         # Trigger for section
2963         if ($line =~ /^Section:\s/){
2964             ($section)= ($line =~ /^Section: (.*)$/);
2965             next;
2966         }
2968         # Trigger for filename
2969         if ($line =~ /^Filename:\s/){
2970             my ($filename) = ($line =~ /^Filename: (.*)$/);
2971             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2972             next;
2973         }
2974     }
2976     close( $PACKAGES );
2977     unlink( "$path.in" );
2981 sub store_fileinfo {
2982     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2984     my %fileinfo = (
2985         'package' => $package,
2986         'dist' => $dist,
2987         'version' => $vers,
2988     );
2990     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2994 sub cleanup_and_extract {
2995     my $fileinfo = $repo_files{ $File::Find::name };
2997     if( defined $fileinfo ) {
2999         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3000         my $sql;
3001         my $package = $fileinfo->{ 'package' };
3002         my $newver = $fileinfo->{ 'version' };
3004         mkpath($dir);
3005         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3007                 if( -f "$dir/DEBIAN/templates" ) {
3009                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3011                         my $tmpl= "";
3012                         {
3013                                 local $/=undef;
3014                                 open FILE, "$dir/DEBIAN/templates";
3015                                 $tmpl = &encode_base64(<FILE>);
3016                                 close FILE;
3017                         }
3018                         rmtree("$dir/DEBIAN/templates");
3020                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3021                 push @packages_list_statements, $sql;
3022                 }
3023     }
3025     return;
3029 sub register_at_foreign_servers {   
3030     my ($kernel) = $_[KERNEL];
3032     # hole alle bekannten server aus known_server_db
3033     my $server_sql = "SELECT * FROM $known_server_tn";
3034     my $server_res = $known_server_db->exec_statement($server_sql);
3036     # no entries in known_server_db
3037     if (not ref(@$server_res[0]) eq "ARRAY") { 
3038         # TODO
3039     }
3041     # detect already connected clients
3042     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3043     my $client_res = $known_clients_db->exec_statement($client_sql);
3045     # send my server details to all other gosa-si-server within the network
3046     foreach my $hit (@$server_res) {
3047         my $hostname = @$hit[0];
3048         my $hostkey = &create_passwd;
3050         # add already connected clients to registration message 
3051         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3052         &add_content2xml_hash($myhash, 'key', $hostkey);
3053         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3055         # add locally loaded gosa-si modules to registration message
3056         my $loaded_modules = {};
3057         while (my ($package, $pck_info) = each %$known_modules) {
3058                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3059                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3060                                                         $loaded_modules->{$act_module} = ""; 
3061                                                 }
3062         }
3064         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3066         # add macaddress to registration message
3067         my ($host_ip, $host_port) = split(/:/, $hostname);
3068         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3069         my $network_interface= &get_interface_for_ip($local_ip);
3070         my $host_mac = &get_mac_for_interface($network_interface);
3071         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3072         
3073         # build registration message and send it
3074         my $foreign_server_msg = &create_xml_string($myhash);
3075         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3076     }
3077     
3078     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3079     return;
3083 #==== MAIN = main ==============================================================
3084 #  parse commandline options
3085 Getopt::Long::Configure( "bundling" );
3086 GetOptions("h|help" => \&usage,
3087         "c|config=s" => \$cfg_file,
3088         "f|foreground" => \$foreground,
3089         "v|verbose+" => \$verbose,
3090         "no-arp+" => \$no_arp,
3091            );
3093 #  read and set config parameters
3094 &check_cmdline_param ;
3095 &read_configfile($cfg_file, %cfg_defaults);
3096 &check_pid;
3098 $SIG{CHLD} = 'IGNORE';
3100 # forward error messages to logfile
3101 if( ! $foreground ) {
3102   open( STDIN,  '+>/dev/null' );
3103   open( STDOUT, '+>&STDIN'    );
3104   open( STDERR, '+>&STDIN'    );
3107 # Just fork, if we are not in foreground mode
3108 if( ! $foreground ) { 
3109     chdir '/'                 or die "Can't chdir to /: $!";
3110     $pid = fork;
3111     setsid                    or die "Can't start a new session: $!";
3112     umask 0;
3113 } else { 
3114     $pid = $$; 
3117 # Do something useful - put our PID into the pid_file
3118 if( 0 != $pid ) {
3119     open( LOCK_FILE, ">$pid_file" );
3120     print LOCK_FILE "$pid\n";
3121     close( LOCK_FILE );
3122     if( !$foreground ) { 
3123         exit( 0 ) 
3124     };
3127 # parse head url and revision from svn
3128 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3129 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3130 $server_headURL = defined $1 ? $1 : 'unknown' ;
3131 $server_revision = defined $2 ? $2 : 'unknown' ;
3132 if ($server_headURL =~ /\/tag\// || 
3133         $server_headURL =~ /\/branches\// ) {
3134     $server_status = "stable"; 
3135 } else {
3136     $server_status = "developmental" ;
3139 # Prepare log file
3140 my $root_uid = getpwnam('root');
3141 my $adm_gid = getgrnam('adm');
3142 chmod(0640, $log_file);
3143 chown($root_uid, $adm_gid, $log_file);
3146 daemon_log(" ", 1);
3147 daemon_log("$0 started!", 1);
3148 daemon_log("status: $server_status", 1);
3149 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3151 # connect to incoming_db
3152 unlink($incoming_file_name);
3153 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3154 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3156 # connect to gosa-si job queue
3157 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3158 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3160 # connect to known_clients_db
3161 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3162 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3164 # connect to foreign_clients_db
3165 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3166 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3168 # connect to known_server_db
3169 unlink($known_server_file_name);
3170 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3171 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3173 # connect to login_usr_db
3174 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3175 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3177 # connect to fai_server_db and fai_release_db
3178 unlink($fai_server_file_name);
3179 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3180 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3182 unlink($fai_release_file_name);
3183 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3184 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3186 # connect to packages_list_db
3187 #unlink($packages_list_file_name);
3188 unlink($packages_list_under_construction);
3189 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3190 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3192 # connect to messaging_db
3193 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3194 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3197 # create xml object used for en/decrypting
3198 $xml = new XML::Simple();
3201 # foreign servers 
3202 my @foreign_server_list;
3204 # add foreign server from cfg file
3205 if ($foreign_server_string ne "") {
3206     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3207     foreach my $foreign_server (@cfg_foreign_server_list) {
3208         push(@foreign_server_list, $foreign_server);
3209     }
3211     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3214 # Perform a DNS lookup for server registration if flag is true
3215 if ($dns_lookup eq "true") {
3216     # Add foreign server from dns
3217     my @tmp_servers;
3218     if (not $server_domain) {
3219         # Try our DNS Searchlist
3220         for my $domain(get_dns_domains()) {
3221             chomp($domain);
3222             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3223             if(@$tmp_domains) {
3224                 for my $tmp_server(@$tmp_domains) {
3225                     push @tmp_servers, $tmp_server;
3226                 }
3227             }
3228         }
3229         if(@tmp_servers && length(@tmp_servers)==0) {
3230             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3231         }
3232     } else {
3233         @tmp_servers = &get_server_addresses($server_domain);
3234         if( 0 == @tmp_servers ) {
3235             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3236         }
3237     }
3239     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3241     foreach my $server (@tmp_servers) { 
3242         unshift(@foreign_server_list, $server); 
3243     }
3244 } else {
3245     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3249 # eliminate duplicate entries
3250 @foreign_server_list = &del_doubles(@foreign_server_list);
3251 my $all_foreign_server = join(", ", @foreign_server_list);
3252 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3254 # add all found foreign servers to known_server
3255 my $act_timestamp = &get_time();
3256 foreach my $foreign_server (@foreign_server_list) {
3258         # do not add myself to known_server_db
3259         if (&is_local($foreign_server)) { next; }
3260         ######################################
3262     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3263             primkey=>['hostname'],
3264             hostname=>$foreign_server,
3265             macaddress=>"",
3266             status=>'not_jet_registered',
3267             hostkey=>"none",
3268             loaded_modules => "none", 
3269             timestamp=>$act_timestamp,
3270             } );
3274 # Import all modules
3275 &import_modules;
3277 # Check wether all modules are gosa-si valid passwd check
3278 &password_check;
3280 # Prepare for using Opsi 
3281 if ($opsi_enabled eq "true") {
3282     use JSON::RPC::Client;
3283     use XML::Quote qw(:all);
3284     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3285     $opsi_client = new JSON::RPC::Client;
3289 POE::Component::Server::TCP->new(
3290     Alias => "TCP_SERVER",
3291         Port => $server_port,
3292         ClientInput => sub {
3293         my ($kernel, $input) = @_[KERNEL, ARG0];
3294         push(@tasks, $input);
3295         push(@msgs_to_decrypt, $input);
3296         $kernel->yield("msg_to_decrypt");
3297         },
3298     InlineStates => {
3299         msg_to_decrypt => \&msg_to_decrypt,
3300         next_task => \&next_task,
3301         task_result => \&handle_task_result,
3302         task_done   => \&handle_task_done,
3303         task_debug  => \&handle_task_debug,
3304         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3305     }
3306 );
3308 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3310 # create session for repeatedly checking the job queue for jobs
3311 POE::Session->create(
3312         inline_states => {
3313                 _start => \&session_start,
3314         register_at_foreign_servers => \&register_at_foreign_servers,
3315         sig_handler => \&sig_handler,
3316         next_task => \&next_task,
3317         task_result => \&handle_task_result,
3318         task_done   => \&handle_task_done,
3319         task_debug  => \&handle_task_debug,
3320         watch_for_next_tasks => \&watch_for_next_tasks,
3321         watch_for_new_messages => \&watch_for_new_messages,
3322         watch_for_delivery_messages => \&watch_for_delivery_messages,
3323         watch_for_done_messages => \&watch_for_done_messages,
3324                 watch_for_new_jobs => \&watch_for_new_jobs,
3325         watch_for_modified_jobs => \&watch_for_modified_jobs,
3326         watch_for_done_jobs => \&watch_for_done_jobs,
3327         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3328         watch_for_old_known_clients => \&watch_for_old_known_clients,
3329         create_packages_list_db => \&run_create_packages_list_db,
3330         create_fai_server_db => \&run_create_fai_server_db,
3331         create_fai_release_db => \&run_create_fai_release_db,
3332                 recreate_packages_db => \&run_recreate_packages_db,
3333         session_run_result => \&session_run_result,
3334         session_run_debug => \&session_run_debug,
3335         session_run_done => \&session_run_done,
3336         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3337         }
3338 );
3341 POE::Kernel->run();
3342 exit;