Code

Updated application acls
[gosa.git] / gosa-si / gosa-si-server-nobus
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);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file, 
86     $server_ip, $server_port, $ClientPackages_key, 
87     $arp_activ, $gosa_unit_tag,
88     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
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);
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_bus = 0;
115 $bus_activ = "true";
116 $no_arp = 0;
117 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
118 my @packages_list_statements;
119 my $watch_for_new_jobs_in_progress = 0;
121 # holds all incoming decrypted messages
122 our $incoming_db;
123 our $incoming_tn = 'incoming';
124 my $incoming_file_name;
125 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
126         "timestamp DEFAULT 'none'", 
127         "headertag DEFAULT 'none'",
128                 "targettag DEFAULT 'none'",
129         "xmlmessage DEFAULT 'none'",
130         "module DEFAULT 'none'",
131         "sessionid DEFAULT '0'",
132         );
134 # holds all gosa jobs
135 our $job_db;
136 our $job_queue_tn = 'jobs';
137 my $job_queue_file_name;
138 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
139                 "timestamp DEFAULT 'none'", 
140                 "status DEFAULT 'none'", 
141                 "result DEFAULT 'none'", 
142                 "progress DEFAULT 'none'", 
143         "headertag DEFAULT 'none'", 
144                 "targettag DEFAULT 'none'", 
145                 "xmlmessage DEFAULT 'none'", 
146                 "macaddress DEFAULT 'none'",
147                 "plainname DEFAULT 'none'",
148                 );
150 # holds all other gosa-sd as well as the gosa-sd-bus
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", "status", "hostkey", "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 %cfg_defaults = (
210 "general" => {
211     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
212     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
213     },
214 "bus" => {
215     "activ" => [\$bus_activ, "true"],
216     },
217 "server" => {
218     "port" => [\$server_port, "20081"],
219     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227     "foreign-clients"      => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
229     "repo-path"            => [\$repo_path, '/srv/www/repository'],
230     "ldap-uri"             => [\$ldap_uri, ""],
231     "ldap-base"            => [\$ldap_base, ""],
232     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
233     "ldap-admin-password"  => [\$ldap_admin_password, ""],
234     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
235     "max-clients"          => [\$max_clients, 10],
236     },
237 "GOsaPackages" => {
238     "ip" => [\$gosa_ip, "0.0.0.0"],
239     "port" => [\$gosa_port, "20082"],
240     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
241     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
242     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
243     "key" => [\$GosaPackages_key, "none"],
244     },
245 "ClientPackages" => {
246     "key" => [\$ClientPackages_key, "none"],
247     },
248 "ServerPackages"=> {
249     "address"      => [\$foreign_server_string, ""],
250     "domain"  => [\$server_domain, ""],
251     "key"     => [\$ServerPackages_key, "none"],
252     "key-lifetime" => [\$foreign_servers_register_delay, 120],
254 );
257 #===  FUNCTION  ================================================================
258 #         NAME:  usage
259 #   PARAMETERS:  nothing
260 #      RETURNS:  nothing
261 #  DESCRIPTION:  print out usage text to STDERR
262 #===============================================================================
263 sub usage {
264     print STDERR << "EOF" ;
265 usage: $prg [-hvf] [-c config]
267            -h        : this (help) message
268            -c <file> : config file
269            -f        : foreground, process will not be forked to background
270            -v        : be verbose (multiple to increase verbosity)
271            -no-bus   : starts $prg without connection to bus
272            -no-arp   : starts $prg without connection to arp module
273  
274 EOF
275     print "\n" ;
279 #===  FUNCTION  ================================================================
280 #         NAME:  read_configfile
281 #   PARAMETERS:  cfg_file - string -
282 #      RETURNS:  nothing
283 #  DESCRIPTION:  read cfg_file and set variables
284 #===============================================================================
285 sub read_configfile {
286     my $cfg;
287     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
288         if( -r $cfg_file ) {
289             $cfg = Config::IniFiles->new( -file => $cfg_file );
290         } else {
291             print STDERR "Couldn't read config file!\n";
292         }
293     } else {
294         $cfg = Config::IniFiles->new() ;
295     }
296     foreach my $section (keys %cfg_defaults) {
297         foreach my $param (keys %{$cfg_defaults{ $section }}) {
298             my $pinfo = $cfg_defaults{ $section }{ $param };
299             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
300         }
301     }
305 #===  FUNCTION  ================================================================
306 #         NAME:  logging
307 #   PARAMETERS:  level - string - default 'info'
308 #                msg - string -
309 #                facility - string - default 'LOG_DAEMON'
310 #      RETURNS:  nothing
311 #  DESCRIPTION:  function for logging
312 #===============================================================================
313 sub daemon_log {
314     # log into log_file
315     my( $msg, $level ) = @_;
316     if(not defined $msg) { return }
317     if(not defined $level) { $level = 1 }
318     if(defined $log_file){
319         open(LOG_HANDLE, ">>$log_file");
320         chmod 0600, $log_file;
321         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
322             print STDERR "cannot open $log_file: $!";
323             return }
324             chomp($msg);
325                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
326             if($level <= $verbose){
327                 my ($seconds, $minutes, $hours, $monthday, $month,
328                         $year, $weekday, $yearday, $sommertime) = localtime(time);
329                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
330                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
331                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
332                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
333                 $month = $monthnames[$month];
334                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
335                 $year+=1900;
336                 my $name = $prg;
338                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
339                 print LOG_HANDLE $log_msg;
340                 if( $foreground ) { 
341                     print STDERR $log_msg;
342                 }
343             }
344         close( LOG_HANDLE );
345     }
349 #===  FUNCTION  ================================================================
350 #         NAME:  check_cmdline_param
351 #   PARAMETERS:  nothing
352 #      RETURNS:  nothing
353 #  DESCRIPTION:  validates commandline parameter
354 #===============================================================================
355 sub check_cmdline_param () {
356     my $err_config;
357     my $err_counter = 0;
358         if(not defined($cfg_file)) {
359                 $cfg_file = "/etc/gosa-si/server.conf";
360                 if(! -r $cfg_file) {
361                         $err_config = "please specify a config file";
362                         $err_counter += 1;
363                 }
364     }
365     if( $err_counter > 0 ) {
366         &usage( "", 1 );
367         if( defined( $err_config)) { print STDERR "$err_config\n"}
368         print STDERR "\n";
369         exit( -1 );
370     }
374 #===  FUNCTION  ================================================================
375 #         NAME:  check_pid
376 #   PARAMETERS:  nothing
377 #      RETURNS:  nothing
378 #  DESCRIPTION:  handels pid processing
379 #===============================================================================
380 sub check_pid {
381     $pid = -1;
382     # Check, if we are already running
383     if( open(LOCK_FILE, "<$pid_file") ) {
384         $pid = <LOCK_FILE>;
385         if( defined $pid ) {
386             chomp( $pid );
387             if( -f "/proc/$pid/stat" ) {
388                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
389                 if( $stat ) {
390                                         daemon_log("ERROR: Already running",1);
391                     close( LOCK_FILE );
392                     exit -1;
393                 }
394             }
395         }
396         close( LOCK_FILE );
397         unlink( $pid_file );
398     }
400     # create a syslog msg if it is not to possible to open PID file
401     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
402         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
403         if (open(LOCK_FILE, '<', $pid_file)
404                 && ($pid = <LOCK_FILE>))
405         {
406             chomp($pid);
407             $msg .= "(PID $pid)\n";
408         } else {
409             $msg .= "(unable to read PID)\n";
410         }
411         if( ! ($foreground) ) {
412             openlog( $0, "cons,pid", "daemon" );
413             syslog( "warning", $msg );
414             closelog();
415         }
416         else {
417             print( STDERR " $msg " );
418         }
419         exit( -1 );
420     }
423 #===  FUNCTION  ================================================================
424 #         NAME:  import_modules
425 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
426 #                are stored
427 #      RETURNS:  nothing
428 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
429 #                state is on is imported by "require 'file';"
430 #===============================================================================
431 sub import_modules {
432     daemon_log(" ", 1);
434     if (not -e $modules_path) {
435         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
436     }
438     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
439     while (defined (my $file = readdir (DIR))) {
440         if (not $file =~ /(\S*?).pm$/) {
441             next;
442         }
443                 my $mod_name = $1;
445         if( $file =~ /ArpHandler.pm/ ) {
446             if( $no_arp > 0 ) {
447                 next;
448             }
449         }
450         
451         eval { require $file; };
452         if ($@) {
453             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
454             daemon_log("$@", 5);
455                 } else {
456                         my $info = eval($mod_name.'::get_module_info()');
457                         # Only load module if get_module_info() returns a non-null object
458                         if( $info ) {
459                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
460                                 $known_modules->{$mod_name} = $info;
461                                 daemon_log("0 INFO: module $mod_name loaded", 5);
462                         }
463                 }
464     }   
465     close (DIR);
469 #===  FUNCTION  ================================================================
470 #         NAME:  sig_int_handler
471 #   PARAMETERS:  signal - string - signal arose from system
472 #      RETURNS:  noting
473 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
474 #===============================================================================
475 sub sig_int_handler {
476     my ($signal) = @_;
478 #       if (defined($ldap_handle)) {
479 #               $ldap_handle->disconnect;
480 #       }
481     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
482     
484     daemon_log("shutting down gosa-si-server", 1);
485     system("kill `ps -C gosa-si-server-nobus -o pid=`");
487 $SIG{INT} = \&sig_int_handler;
490 sub check_key_and_xml_validity {
491     my ($crypted_msg, $module_key, $session_id) = @_;
492     my $msg;
493     my $msg_hash;
494     my $error_string;
495     eval{
496         $msg = &decrypt_msg($crypted_msg, $module_key);
498         if ($msg =~ /<xml>/i){
499             $msg =~ s/\s+/ /g;  # just for better daemon_log
500             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
501             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
503             ##############
504             # check header
505             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
506             my $header_l = $msg_hash->{'header'};
507             if( 1 > @{$header_l} ) { die 'empty header tag'; }
508             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
509             my $header = @{$header_l}[0];
510             if( 0 == length $header) { die 'empty string in header tag'; }
512             ##############
513             # check source
514             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
515             my $source_l = $msg_hash->{'source'};
516             if( 1 > @{$source_l} ) { die 'empty source tag'; }
517             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
518             my $source = @{$source_l}[0];
519             if( 0 == length $source) { die 'source error'; }
521             ##############
522             # check target
523             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
524             my $target_l = $msg_hash->{'target'};
525             if( 1 > @{$target_l} ) { die 'empty target tag'; }
526         }
527     };
528     if($@) {
529         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
530         $msg = undef;
531         $msg_hash = undef;
532     }
534     return ($msg, $msg_hash);
538 sub check_outgoing_xml_validity {
539     my ($msg) = @_;
541     my $msg_hash;
542     eval{
543         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
545         ##############
546         # check header
547         my $header_l = $msg_hash->{'header'};
548         if( 1 != @{$header_l} ) {
549             die 'no or more than one headers specified';
550         }
551         my $header = @{$header_l}[0];
552         if( 0 == length $header) {
553             die 'header has length 0';
554         }
556         ##############
557         # check source
558         my $source_l = $msg_hash->{'source'};
559         if( 1 != @{$source_l} ) {
560             die 'no or more than 1 sources specified';
561         }
562         my $source = @{$source_l}[0];
563         if( 0 == length $source) {
564             die 'source has length 0';
565         }
566         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
567                 $source =~ /^GOSA$/i ) {
568             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
569         }
570         
571         ##############
572         # check target  
573         my $target_l = $msg_hash->{'target'};
574         if( 0 == @{$target_l} ) {
575             die "no targets specified";
576         }
577         foreach my $target (@$target_l) {
578             if( 0 == length $target) {
579                 die "target has length 0";
580             }
581             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582                     $target =~ /^GOSA$/i ||
583                     $target =~ /^\*$/ ||
584                     $target =~ /KNOWN_SERVER/i ||
585                     $target =~ /JOBDB/i ||
586                     $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 ){
587                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
588             }
589         }
590     };
591     if($@) {
592         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
593         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
594         $msg_hash = undef;
595     }
597     return ($msg_hash);
601 sub input_from_known_server {
602     my ($input, $remote_ip, $session_id) = @_ ;  
603     my ($msg, $msg_hash, $module);
605     my $sql_statement= "SELECT * FROM known_server";
606     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
608     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
609         my $host_name = $hit->{hostname};
610         if( not $host_name =~ "^$remote_ip") {
611             next;
612         }
613         my $host_key = $hit->{hostkey};
614         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
615         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
617         # check if module can open msg envelope with module key
618         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
619         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
620             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
621             daemon_log("$@", 8);
622             next;
623         }
624         else {
625             $msg = $tmp_msg;
626             $msg_hash = $tmp_msg_hash;
627             $module = "ServerPackages";
628             last;
629         }
630     }
632     if( (!$msg) || (!$msg_hash) || (!$module) ) {
633         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
634     }
635   
636     return ($msg, $msg_hash, $module);
640 sub input_from_known_client {
641     my ($input, $remote_ip, $session_id) = @_ ;  
642     my ($msg, $msg_hash, $module);
644     my $sql_statement= "SELECT * FROM known_clients";
645     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
646     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
647         my $host_name = $hit->{hostname};
648         if( not $host_name =~ /^$remote_ip:\d*$/) {
649                 next;
650                 }
651         my $host_key = $hit->{hostkey};
652         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
653         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
655         # check if module can open msg envelope with module key
656         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
658         if( (!$msg) || (!$msg_hash) ) {
659             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
660             &daemon_log("$@", 8);
661             next;
662         }
663         else {
664             $module = "ClientPackages";
665             last;
666         }
667     }
669     if( (!$msg) || (!$msg_hash) || (!$module) ) {
670         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
671     }
673     return ($msg, $msg_hash, $module);
677 sub input_from_unknown_host {
678     no strict "refs";
679     my ($input, $session_id) = @_ ;
680     my ($msg, $msg_hash, $module);
681     my $error_string;
682     
683         my %act_modules = %$known_modules;
684         
685     while( my ($mod, $info) = each(%act_modules)) {
687         # check a key exists for this module
688         my $module_key = ${$mod."_key"};
689         if( not defined $module_key ) {
690             if( $mod eq 'ArpHandler' ) {
691                 next;
692             }
693             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
694             next;
695         }
696         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
698         # check if module can open msg envelope with module key
699         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
700         if( (not defined $msg) || (not defined $msg_hash) ) {
701             next;
702         }
703         else {
704             $module = $mod;
705             last;
706         }
707     }
709     if( (!$msg) || (!$msg_hash) || (!$module)) {
710         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
711     }
713     return ($msg, $msg_hash, $module);
717 sub create_ciphering {
718     my ($passwd) = @_;
719         if((!defined($passwd)) || length($passwd)==0) {
720                 $passwd = "";
721         }
722     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
723     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
724     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
725     $my_cipher->set_iv($iv);
726     return $my_cipher;
730 sub encrypt_msg {
731     my ($msg, $key) = @_;
732     my $my_cipher = &create_ciphering($key);
733     my $len;
734     {
735             use bytes;
736             $len= 16-length($msg)%16;
737     }
738     $msg = "\0"x($len).$msg;
739     $msg = $my_cipher->encrypt($msg);
740     chomp($msg = &encode_base64($msg));
741     # there are no newlines allowed inside msg
742     $msg=~ s/\n//g;
743     return $msg;
747 sub decrypt_msg {
749     my ($msg, $key) = @_ ;
750     $msg = &decode_base64($msg);
751     my $my_cipher = &create_ciphering($key);
752     $msg = $my_cipher->decrypt($msg); 
753     $msg =~ s/\0*//g;
754     return $msg;
758 sub get_encrypt_key {
759     my ($target) = @_ ;
760     my $encrypt_key;
761     my $error = 0;
763     # target can be in known_server
764     if( not defined $encrypt_key ) {
765         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
766         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
767         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
768             my $host_name = $hit->{hostname};
769             if( $host_name ne $target ) {
770                 next;
771             }
772             $encrypt_key = $hit->{hostkey};
773             last;
774         }
775     }
777     # target can be in known_client
778     if( not defined $encrypt_key ) {
779         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
780         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
781         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
782             my $host_name = $hit->{hostname};
783             if( $host_name ne $target ) {
784                 next;
785             }
786             $encrypt_key = $hit->{hostkey};
787             last;
788         }
789     }
791     return $encrypt_key;
795 #===  FUNCTION  ================================================================
796 #         NAME:  open_socket
797 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
798 #                [PeerPort] string necessary if port not appended by PeerAddr
799 #      RETURNS:  socket IO::Socket::INET
800 #  DESCRIPTION:  open a socket to PeerAddr
801 #===============================================================================
802 sub open_socket {
803     my ($PeerAddr, $PeerPort) = @_ ;
804     if(defined($PeerPort)){
805         $PeerAddr = $PeerAddr.":".$PeerPort;
806     }
807     my $socket;
808     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
809             Porto => "tcp",
810             Type => SOCK_STREAM,
811             Timeout => 5,
812             );
813     if(not defined $socket) {
814         return;
815     }
816 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
817     return $socket;
821 #===  FUNCTION  ================================================================
822 #         NAME:  get_ip 
823 #   PARAMETERS:  interface name (i.e. eth0)
824 #      RETURNS:  (ip address) 
825 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
826 #===============================================================================
827 sub get_ip {
828         my $ifreq= shift;
829         my $result= "";
830         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
831         my $proto= getprotobyname('ip');
833         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
834                 or die "socket: $!";
836         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
837                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
838                 my ($port, $addr) = sockaddr_in $sin;
839                 my $ip            = inet_ntoa $addr;
841                 if ($ip && length($ip) > 0) {
842                         $result = $ip;
843                 }
844         }
846         return $result;
850 sub get_local_ip_for_remote_ip {
851         my $remote_ip= shift;
852         my $result="0.0.0.0";
854         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855                 if($remote_ip eq "127.0.0.1") {
856                         $result = "127.0.0.1";
857                 } else {
858                         my $PROC_NET_ROUTE= ('/proc/net/route');
860                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861                                 or die "Could not open $PROC_NET_ROUTE";
863                         my @ifs = <PROC_NET_ROUTE>;
865                         close(PROC_NET_ROUTE);
867                         # Eat header line
868                         shift @ifs;
869                         chomp @ifs;
870                         foreach my $line(@ifs) {
871                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872                                 my $destination;
873                                 my $mask;
874                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879                                         # destination matches route, save mac and exit
880                                         $result= &get_ip($Iface);
881                                         last;
882                                 }
883                         }
884                 }
885         } else {
886                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
887         }
888         return $result;
892 sub send_msg_to_target {
893     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894     my $error = 0;
895     my $header;
896     my $timestamp = &get_time();
897     my $new_status;
898     my $act_status;
899     my ($sql_statement, $res);
900   
901     if( $msg_header ) {
902         $header = "'$msg_header'-";
903     } else {
904         $header = "";
905     }
907         # Patch the source ip
908         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911         }
913     # encrypt xml msg
914     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916     # opensocket
917     my $socket = &open_socket($address);
918     if( !$socket ) {
919         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
920         $error++;
921     }
922     
923     if( $error == 0 ) {
924         # send xml msg
925         print $socket $crypted_msg."\n";
927         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928         daemon_log("DEBUG: message:\n$msg", 9);
929         
930     }
932     # close socket in any case
933     if( $socket ) {
934         close $socket;
935     }
937     if( $error > 0 ) { $new_status = "down"; }
938     else { $new_status = $msg_header; }
941     # known_clients
942     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
943     $res = $known_clients_db->select_dbentry($sql_statement);
944     if( keys(%$res) == 1) {
945         $act_status = $res->{1}->{'status'};
946         if ($act_status eq "down" && $new_status eq "down") {
947             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948             $res = $known_clients_db->del_dbentry($sql_statement);
949             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950         } else { 
951             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952             $res = $known_clients_db->update_dbentry($sql_statement);
953             if($new_status eq "down"){
954                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955             } else {
956                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957             }
958         }
959     }
961     # known_server
962     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963     $res = $known_server_db->select_dbentry($sql_statement);
964     if( keys(%$res) == 1) {
965         $act_status = $res->{1}->{'status'};
966         if ($act_status eq "down" && $new_status eq "down") {
967             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968             $res = $known_server_db->del_dbentry($sql_statement);
969             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970         } 
971         else { 
972             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973             $res = $known_server_db->update_dbentry($sql_statement);
974             if($new_status eq "down"){
975                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976             }
977             else {
978                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
979             }
980         }
981     }
982     return $error; 
986 sub update_jobdb_status_for_send_msgs {
987     my ($answer, $error) = @_;
988     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
989         my $jobdb_id = $1;
990             
991         # sending msg faild
992         if( $error ) {
993             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
994                 my $sql_statement = "UPDATE $job_queue_tn ".
995                     "SET status='error', result='can not deliver msg, please consult log file' ".
996                     "WHERE id=$jobdb_id";
997                 my $res = $job_db->update_dbentry($sql_statement);
998             }
1000         # sending msg was successful
1001         } else {
1002             my $sql_statement = "UPDATE $job_queue_tn ".
1003                 "SET status='done' ".
1004                 "WHERE id=$jobdb_id AND status='processed'";
1005             my $res = $job_db->update_dbentry($sql_statement);
1006         }
1007     }
1011 sub sig_handler {
1012         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013         daemon_log("0 INFO got signal '$signal'", 1); 
1014         $kernel->sig_handled();
1015         return;
1019 sub msg_to_decrypt {
1020     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1021     my $session_id = $session->ID;
1022     my ($msg, $msg_hash, $module);
1023     my $error = 0;
1025     # hole neue msg aus @msgs_to_decrypt
1026     my $next_msg = shift @msgs_to_decrypt;
1027     
1028     # entschlüssle sie
1030     # msg is from a new client or gosa
1031     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032     # msg is from a gosa-si-server or gosa-si-bus
1033     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035     }
1036     # msg is from a gosa-si-client
1037     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039     }
1040     # an error occurred
1041     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043         # could not understand a msg from its server the client cause a re-registering process
1044         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1045         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1047         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1048             my $host_name = $hit->{'hostname'};
1049             my $host_key = $hit->{'hostkey'};
1050             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053         }
1054         $error++;
1055     }
1056     
1057     # add message to incoming_db
1058     if( $error == 0) {
1059         my $header = @{$msg_hash->{'header'}}[0];
1060         my $target = @{$msg_hash->{'target'}}[0];
1061         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1062                 primkey=>[],
1063                 headertag=>$header,
1064                                 targettag=>$target,
1065                 xmlmessage=>$msg,
1066                 timestamp=>&get_time,
1067                 module=>$module,
1068                 sessionid=>$session_id,
1069                 } );
1070         if ($res != 0) {
1071                         # TODO ist das mit $! so ok???
1072             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1073         }
1074     }
1079 sub next_task {
1080     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1081     my $running_task = POE::Wheel::Run->new(
1082             Program => sub { process_task($session, $heap, $task) },
1083             StdioFilter => POE::Filter::Reference->new(),
1084             StdoutEvent  => "task_result",
1085             StderrEvent  => "task_debug",
1086             CloseEvent   => "task_done",
1087             );
1088     $heap->{task}->{ $running_task->ID } = $running_task;
1091 sub handle_task_result {
1092     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1093     my $client_answer = $result->{'answer'};
1094     if( $client_answer =~ s/session_id=(\d+)$// ) {
1095         my $session_id = $1;
1096         if( defined $session_id ) {
1097             my $session_reference = $kernel->ID_id_to_session($session_id);
1098             if( defined $session_reference ) {
1099                 $heap = $session_reference->get_heap();
1100             }
1101         }
1103         if(exists $heap->{'client'}) {
1104             $heap->{'client'}->put($client_answer);
1105         }
1106     }
1107     $kernel->sig(CHLD => "child_reap");
1110 sub handle_task_debug {
1111     my $result = $_[ARG0];
1112     print STDERR "$result\n";
1115 sub handle_task_done {
1116     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1117     delete $heap->{task}->{$task_id};
1120 sub process_task {
1121     no strict "refs";
1122     my ($session, $heap, $task) = @_;
1123     my $error = 0;
1124     my $answer_l;
1125     my ($answer_header, @answer_target_l, $answer_source);
1126     my $client_answer = "";
1128     # prepare all variables needed to process message
1129     my $msg = $task->{'xmlmessage'};
1130     my $incoming_id = $task->{'id'};
1131     my $module = $task->{'module'};
1132     my $header =  $task->{'headertag'};
1133     my $session_id = $task->{'sessionid'};
1134     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1135     my $source = @{$msg_hash->{'source'}}[0];
1136     
1137     # set timestamp of incoming client uptodate, so client will not 
1138     # be deleted from known_clients because of expiration
1139     my $act_time = &get_time();
1140     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1141     my $res = $known_clients_db->exec_statement($sql);
1143     ######################
1144     # process incoming msg
1145     if( $error == 0) {
1146         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1147         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1148         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1150         if ( 0 < @{$answer_l} ) {
1151             my $answer_str = join("\n", @{$answer_l});
1152             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1153                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1154             }
1155             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1156         } else {
1157             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1158         }
1160     }
1161     if( !$answer_l ) { $error++ };
1163     ########
1164     # answer
1165     if( $error == 0 ) {
1167         foreach my $answer ( @{$answer_l} ) {
1168             # check outgoing msg to xml validity
1169             my $answer_hash = &check_outgoing_xml_validity($answer);
1170             if( not defined $answer_hash ) { next; }
1171             
1172             $answer_header = @{$answer_hash->{'header'}}[0];
1173             @answer_target_l = @{$answer_hash->{'target'}};
1174             $answer_source = @{$answer_hash->{'source'}}[0];
1176             # deliver msg to all targets 
1177             foreach my $answer_target ( @answer_target_l ) {
1179                 # targets of msg are all gosa-si-clients in known_clients_db
1180                 if( $answer_target eq "*" ) {
1181                     # answer is for all clients
1182                     my $sql_statement= "SELECT * FROM known_clients";
1183                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1184                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1185                         my $host_name = $hit->{hostname};
1186                         my $host_key = $hit->{hostkey};
1187                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1188                         &update_jobdb_status_for_send_msgs($answer, $error);
1189                     }
1190                 }
1192                 # targets of msg are all gosa-si-server in known_server_db
1193                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1194                     # answer is for all server in known_server
1195                     my $sql_statement= "SELECT * FROM known_server";
1196                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1197                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1198                         my $host_name = $hit->{hostname};
1199                         my $host_key = $hit->{hostkey};
1200                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1201                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1202                         &update_jobdb_status_for_send_msgs($answer, $error);
1203                     }
1204                 }
1206                 # target of msg is GOsa
1207                                 elsif( $answer_target eq "GOSA" ) {
1208                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1209                                         my $add_on = "";
1210                     if( defined $session_id ) {
1211                         $add_on = ".session_id=$session_id";
1212                     }
1213                     # answer is for GOSA and has to returned to connected client
1214                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1215                     $client_answer = $gosa_answer.$add_on;
1216                 }
1218                 # target of msg is job queue at this host
1219                 elsif( $answer_target eq "JOBDB") {
1220                     $answer =~ /<header>(\S+)<\/header>/;   
1221                     my $header;
1222                     if( defined $1 ) { $header = $1; }
1223                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1224                     &update_jobdb_status_for_send_msgs($answer, $error);
1225                 }
1227                 # target of msg is a mac address
1228                 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 ) {
1229                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1230                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1231                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1232                     my $found_ip_flag = 0;
1233                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1234                         my $host_name = $hit->{hostname};
1235                         my $host_key = $hit->{hostkey};
1236                         $answer =~ s/$answer_target/$host_name/g;
1237                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1238                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1239                         &update_jobdb_status_for_send_msgs($answer, $error);
1240                         $found_ip_flag++ ;
1241                     }   
1242                     if( $found_ip_flag == 0) {
1243                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1244                         if( $bus_activ eq "true" ) { 
1245                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1246                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1247                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1248                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1249                                 my $bus_address = $hit->{hostname};
1250                                 my $bus_key = $hit->{hostkey};
1251                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1252                                 &update_jobdb_status_for_send_msgs($answer, $error);
1253                                 last;
1254                             }
1255                         }
1257                     }
1259                 #  answer is for one specific host   
1260                 } else {
1261                     # get encrypt_key
1262                     my $encrypt_key = &get_encrypt_key($answer_target);
1263                     if( not defined $encrypt_key ) {
1264                         # unknown target, forward msg to bus
1265                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1266                         if( $bus_activ eq "true" ) { 
1267                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1268                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1269                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1270                             my $res_length = keys( %{$query_res} );
1271                             if( $res_length == 0 ){
1272                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1273                                         "no bus found in known_server", 3);
1274                             }
1275                             else {
1276                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1277                                     my $bus_key = $hit->{hostkey};
1278                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1279                                     &update_jobdb_status_for_send_msgs($answer, $error);
1280                                 }
1281                             }
1282                         }
1283                         next;
1284                     }
1285                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1286                     &update_jobdb_status_for_send_msgs($answer, $error);
1287                 }
1288             }
1289         }
1290     }
1292     my $filter = POE::Filter::Reference->new();
1293     my %result = ( 
1294             status => "seems ok to me",
1295             answer => $client_answer,
1296             );
1298     my $output = $filter->put( [ \%result ] );
1299     print @$output;
1304 sub session_start {
1305     my ($kernel) = $_[KERNEL];
1306     &trigger_db_loop($kernel);
1307     $global_kernel = $kernel;
1308     $kernel->yield('register_at_foreign_servers');
1309         $kernel->yield('create_fai_server_db', $fai_server_tn );
1310         $kernel->yield('create_fai_release_db', $fai_release_tn );
1311     $kernel->yield('watch_for_next_tasks');
1312         $kernel->sig(USR1 => "sig_handler");
1313         $kernel->sig(USR2 => "create_packages_list_db");
1316 sub trigger_db_loop {
1317         my ($kernel) = @_ ;
1318         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1319         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1320         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1321     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1322         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1323     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1327 sub watch_for_done_jobs {
1328     my ($kernel,$heap) = @_[KERNEL, HEAP];
1330     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1331         " WHERE status='done'";
1332         my $res = $job_db->select_dbentry( $sql_statement );
1334     while( my ($id, $hit) = each %{$res} ) {
1335         my $jobdb_id = $hit->{id};
1336         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1337         my $res = $job_db->del_dbentry($sql_statement); 
1338     }
1340     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1344 sub watch_for_new_jobs {
1345         if($watch_for_new_jobs_in_progress == 0) {
1346                 $watch_for_new_jobs_in_progress = 1;
1347                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1349                 # check gosa job queue for jobs with executable timestamp
1350                 my $timestamp = &get_time();
1351                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1352                 my $res = $job_db->exec_statement( $sql_statement );
1354                 # Merge all new jobs that would do the same actions
1355                 my @drops;
1356                 my $hits;
1357                 foreach my $hit (reverse @{$res} ) {
1358                         my $macaddress= lc @{$hit}[8];
1359                         my $headertag= @{$hit}[5];
1360                         if(
1361                                 defined($hits->{$macaddress}) &&
1362                                 defined($hits->{$macaddress}->{$headertag}) &&
1363                                 defined($hits->{$macaddress}->{$headertag}[0])
1364                         ) {
1365                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1366                         }
1367                         $hits->{$macaddress}->{$headertag}= $hit;
1368                 }
1370                 # Delete new jobs with a matching job in state 'processing'
1371                 foreach my $macaddress (keys %{$hits}) {
1372                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1373                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1374                                 if(defined($jobdb_id)) {
1375                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1376                                         my $res = $job_db->exec_statement( $sql_statement );
1377                                         foreach my $hit (@{$res}) {
1378                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1379                                         }
1380                                 } else {
1381                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1382                                 }
1383                         }
1384                 }
1386                 # Commit deletion
1387                 $job_db->exec_statementlist(\@drops);
1389                 # Look for new jobs that could be executed
1390                 foreach my $macaddress (keys %{$hits}) {
1392                         # Look if there is an executing job
1393                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1394                         my $res = $job_db->exec_statement( $sql_statement );
1396                         # Skip new jobs for host if there is a processing job
1397                         if(defined($res) and defined @{$res}[0]) {
1398                                 next;
1399                         }
1401                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1402                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1403                                 if(defined($jobdb_id)) {
1404                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1406                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1407                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1408                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1410                                         # expect macaddress is unique!!!!!!
1411                                         my $target = $res_hash->{1}->{hostname};
1413                                         # change header
1414                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1416                                         # add sqlite_id
1417                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1419                                         $job_msg =~ /<header>(\S+)<\/header>/;
1420                                         my $header = $1 ;
1421                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1423                                         # update status in job queue to 'processing'
1424                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1425                                         my $res = $job_db->update_dbentry($sql_statement);
1426 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1428                                         # We don't want parallel processing
1429                                         last;
1430                                 }
1431                         }
1432                 }
1434                 $watch_for_new_jobs_in_progress = 0;
1435                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1436         }
1440 sub watch_for_new_messages {
1441     my ($kernel,$heap) = @_[KERNEL, HEAP];
1442     my @coll_user_msg;   # collection list of outgoing messages
1443     
1444     # check messaging_db for new incoming messages with executable timestamp
1445     my $timestamp = &get_time();
1446     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1447     my $res = $messaging_db->exec_statement( $sql_statement );
1448         foreach my $hit (@{$res}) {
1450         # create outgoing messages
1451         my $message_to = @{$hit}[3];
1452         # translate message_to to plain login name
1453         my @message_to_l = split(/,/, $message_to);  
1454                 my %receiver_h; 
1455                 foreach my $receiver (@message_to_l) {
1456                         if ($receiver =~ /^u_([\s\S]*)$/) {
1457                                 $receiver_h{$1} = 0;
1458                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1459                                 my $group_name = $1;
1460                                 # fetch all group members from ldap and add them to receiver hash
1461                                 my $ldap_handle = &get_ldap_handle();
1462                                 if (defined $ldap_handle) {
1463                                                 my $mesg = $ldap_handle->search(
1464                                                                                 base => $ldap_base,
1465                                                                                 scope => 'sub',
1466                                                                                 attrs => ['memberUid'],
1467                                                                                 filter => "cn=$group_name",
1468                                                                                 );
1469                                                 if ($mesg->count) {
1470                                                                 my @entries = $mesg->entries;
1471                                                                 foreach my $entry (@entries) {
1472                                                                                 my @receivers= $entry->get_value("memberUid");
1473                                                                                 foreach my $receiver (@receivers) { 
1474                                                                                                 $receiver_h{$1} = 0;
1475                                                                                 }
1476                                                                 }
1477                                                 } 
1478                                                 # translating errors ?
1479                                                 if ($mesg->code) {
1480                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1481                                                 }
1482                                 # ldap handle error ?           
1483                                 } else {
1484                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1485                                 }
1486                         } else {
1487                                 my $sbjct = &encode_base64(@{$hit}[1]);
1488                                 my $msg = &encode_base64(@{$hit}[7]);
1489                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1490                         }
1491                 }
1492                 my @receiver_l = keys(%receiver_h);
1494         my $message_id = @{$hit}[0];
1496         #add each outgoing msg to messaging_db
1497         my $receiver;
1498         foreach $receiver (@receiver_l) {
1499             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1500                 "VALUES ('".
1501                 $message_id."', '".    # id
1502                 @{$hit}[1]."', '".     # subject
1503                 @{$hit}[2]."', '".     # message_from
1504                 $receiver."', '".      # message_to
1505                 "none"."', '".         # flag
1506                 "out"."', '".          # direction
1507                 @{$hit}[6]."', '".     # delivery_time
1508                 @{$hit}[7]."', '".     # message
1509                 $timestamp."'".     # timestamp
1510                 ")";
1511             &daemon_log("M DEBUG: $sql_statement", 1);
1512             my $res = $messaging_db->exec_statement($sql_statement);
1513             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1514         }
1516         # set incoming message to flag d=deliverd
1517         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1518         &daemon_log("M DEBUG: $sql_statement", 7);
1519         $res = $messaging_db->update_dbentry($sql_statement);
1520         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1521     }
1523     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1524     return;
1527 sub watch_for_delivery_messages {
1528     my ($kernel, $heap) = @_[KERNEL, HEAP];
1530     # select outgoing messages
1531     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1532     #&daemon_log("0 DEBUG: $sql", 7);
1533     my $res = $messaging_db->exec_statement( $sql_statement );
1534     
1535     # build out msg for each    usr
1536     foreach my $hit (@{$res}) {
1537         my $receiver = @{$hit}[3];
1538         my $msg_id = @{$hit}[0];
1539         my $subject = @{$hit}[1];
1540         my $message = @{$hit}[7];
1542         # resolve usr -> host where usr is logged in
1543         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1544         #&daemon_log("0 DEBUG: $sql", 7);
1545         my $res = $login_users_db->exec_statement($sql);
1547         # reciver is logged in nowhere
1548         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1550                 my $send_succeed = 0;
1551                 foreach my $hit (@$res) {
1552                                 my $receiver_host = @$hit[0];
1553                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1555                                 # fetch key to encrypt msg propperly for usr/host
1556                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1557                                 &daemon_log("0 DEBUG: $sql", 7);
1558                                 my $res = $known_clients_db->select_dbentry($sql);
1560                                 # host is already down
1561                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1563                                 # host is on
1564                                 my $receiver_key = @{@{$res}[0]}[2];
1565                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1566                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1567                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1568                                 if ($error == 0 ) {
1569                                         $send_succeed++ ;
1570                                 }
1571                 }
1573                 if ($send_succeed) {
1574                                 # set outgoing msg at db to deliverd
1575                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1576                                 &daemon_log("0 DEBUG: $sql", 7);
1577                                 my $res = $messaging_db->exec_statement($sql); 
1578                 }
1579         }
1581     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1582     return;
1586 sub watch_for_done_messages {
1587     my ($kernel,$heap) = @_[KERNEL, HEAP];
1589     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1590     #&daemon_log("0 DEBUG: $sql", 7);
1591     my $res = $messaging_db->exec_statement($sql); 
1593     foreach my $hit (@{$res}) {
1594         my $msg_id = @{$hit}[0];
1596         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1597         #&daemon_log("0 DEBUG: $sql", 7); 
1598         my $res = $messaging_db->exec_statement($sql);
1600         # not all usr msgs have been seen till now
1601         if ( ref(@$res[0]) eq "ARRAY") { next; }
1602         
1603         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1604         #&daemon_log("0 DEBUG: $sql", 7);
1605         $res = $messaging_db->exec_statement($sql);
1606     
1607     }
1609     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1610     return;
1614 sub watch_for_old_known_clients {
1615     my ($kernel,$heap) = @_[KERNEL, HEAP];
1617     my $sql_statement = "SELECT * FROM $known_clients_tn";
1618     my $res = $known_clients_db->select_dbentry( $sql_statement );
1620     my $act_time = int(&get_time());
1622     while ( my ($hit_num, $hit) = each %$res) {
1623         my $expired_timestamp = int($hit->{'timestamp'});
1624         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1625         my $dt = DateTime->new( year   => $1,
1626                 month  => $2,
1627                 day    => $3,
1628                 hour   => $4,
1629                 minute => $5,
1630                 second => $6,
1631                 );
1633         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1634         $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1635         if ($act_time > $expired_timestamp) {
1636             my $hostname = $hit->{'hostname'};
1637             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
1638             my $del_res = $known_clients_db->exec_statement($del_sql);
1640             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1641         }
1643     }
1645     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1649 sub watch_for_next_tasks {
1650     my ($kernel,$heap) = @_[KERNEL, HEAP];
1651     
1652     my $sql = "SELECT * FROM $incoming_tn";
1653     my $res = $incoming_db->select_dbentry($sql);
1655     while ( my ($hit_num, $hit) = each %$res) {
1656         my $headertag = $hit->{'headertag'};
1657         if ($headertag =~ /^answer_(\d+)/) {
1658             # do not start processing, this message is for a still running POE::Wheel
1659             next;
1660         }
1661         my $message_id = $hit->{'id'};
1662         $kernel->yield('next_task', $hit);
1664         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1665         my $res = $incoming_db->exec_statement($sql);
1666     }
1668     $kernel->delay_set('watch_for_next_tasks', 1); 
1672 sub get_ldap_handle {
1673         my ($session_id) = @_;
1674         my $heap;
1675         my $ldap_handle;
1677         if (not defined $session_id ) { $session_id = 0 };
1678         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1680         if ($session_id == 0) {
1681                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1682                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1683                 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password); 
1685         } else {
1686                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1687                 if( defined $session_reference ) {
1688                         $heap = $session_reference->get_heap();
1689                 }
1691                 if (not defined $heap) {
1692                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1693                         return;
1694                 }
1696                 # TODO: This "if" is nonsense, because it doesn't prove that the
1697                 #       used handle is still valid - or if we've to reconnect...
1698                 #if (not exists $heap->{ldap_handle}) {
1699                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1700                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1701                         $heap->{ldap_handle} = $ldap_handle;
1702                 #}
1703         }
1704         return $ldap_handle;
1708 sub change_fai_state {
1709     my ($st, $targets, $session_id) = @_;
1710     $session_id = 0 if not defined $session_id;
1711     # Set FAI state to localboot
1712     my %mapActions= (
1713         reboot    => '',
1714         update    => 'softupdate',
1715         localboot => 'localboot',
1716         reinstall => 'install',
1717         rescan    => '',
1718         wake      => '',
1719         memcheck  => 'memcheck',
1720         sysinfo   => 'sysinfo',
1721         install   => 'install',
1722     );
1724     # Return if this is unknown
1725     if (!exists $mapActions{ $st }){
1726         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1727       return;
1728     }
1730     my $state= $mapActions{ $st };
1732     my $ldap_handle = &get_ldap_handle($session_id);
1733     if( defined($ldap_handle) ) {
1735       # Build search filter for hosts
1736         my $search= "(&(objectClass=GOhard)";
1737         foreach (@{$targets}){
1738             $search.= "(macAddress=$_)";
1739         }
1740         $search.= ")";
1742       # If there's any host inside of the search string, procress them
1743         if (!($search =~ /macAddress/)){
1744             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1745             return;
1746         }
1748       # Perform search for Unit Tag
1749       my $mesg = $ldap_handle->search(
1750           base   => $ldap_base,
1751           scope  => 'sub',
1752           attrs  => ['dn', 'FAIstate', 'objectClass'],
1753           filter => "$search"
1754           );
1756           if ($mesg->count) {
1757                   my @entries = $mesg->entries;
1758                   foreach my $entry (@entries) {
1759                           # Only modify entry if it is not set to '$state'
1760                           if ($entry->get_value("FAIstate") ne "$state"){
1761                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1762                                   my $result;
1763                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1764                                   if (exists $tmp{'FAIobject'}){
1765                                           if ($state eq ''){
1766                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1767                                                           delete => [ FAIstate => [] ] ]);
1768                                           } else {
1769                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1770                                                           replace => [ FAIstate => $state ] ]);
1771                                           }
1772                                   } elsif ($state ne ''){
1773                                           $result= $ldap_handle->modify($entry->dn, changes => [
1774                                                   add     => [ objectClass => 'FAIobject' ],
1775                                                   add     => [ FAIstate => $state ] ]);
1776                                   }
1778                                   # Errors?
1779                                   if ($result->code){
1780                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1781                                   }
1782                           } else {
1783                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1784                           }  
1785                   }
1786           }
1787     # if no ldap handle defined
1788     } else {
1789         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1790     }
1795 sub change_goto_state {
1796     my ($st, $targets, $session_id) = @_;
1797     $session_id = 0  if not defined $session_id;
1799     # Switch on or off?
1800     my $state= $st eq 'active' ? 'active': 'locked';
1802     my $ldap_handle = &get_ldap_handle($session_id);
1803     if( defined($ldap_handle) ) {
1805       # Build search filter for hosts
1806       my $search= "(&(objectClass=GOhard)";
1807       foreach (@{$targets}){
1808         $search.= "(macAddress=$_)";
1809       }
1810       $search.= ")";
1812       # If there's any host inside of the search string, procress them
1813       if (!($search =~ /macAddress/)){
1814         return;
1815       }
1817       # Perform search for Unit Tag
1818       my $mesg = $ldap_handle->search(
1819           base   => $ldap_base,
1820           scope  => 'sub',
1821           attrs  => ['dn', 'gotoMode'],
1822           filter => "$search"
1823           );
1825       if ($mesg->count) {
1826         my @entries = $mesg->entries;
1827         foreach my $entry (@entries) {
1829           # Only modify entry if it is not set to '$state'
1830           if ($entry->get_value("gotoMode") ne $state){
1832             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1833             my $result;
1834             $result= $ldap_handle->modify($entry->dn, changes => [
1835                                                 replace => [ gotoMode => $state ] ]);
1837             # Errors?
1838             if ($result->code){
1839               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1840             }
1842           }
1843         }
1844       }
1846     }
1850 sub run_create_fai_server_db {
1851     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1852     my $session_id = $session->ID;
1853     my $task = POE::Wheel::Run->new(
1854             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1855             StdoutEvent  => "session_run_result",
1856             StderrEvent  => "session_run_debug",
1857             CloseEvent   => "session_run_done",
1858             );
1860     $heap->{task}->{ $task->ID } = $task;
1861     return;
1865 sub create_fai_server_db {
1866     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1867         my $result;
1869         if (not defined $session_id) { $session_id = 0; }
1870     my $ldap_handle = &get_ldap_handle();
1871         if(defined($ldap_handle)) {
1872                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1873                 my $mesg= $ldap_handle->search(
1874                         base   => $ldap_base,
1875                         scope  => 'sub',
1876                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1877                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1878                 );
1879                 if($mesg->{'resultCode'} == 0 &&
1880                    $mesg->count != 0) {
1881                    foreach my $entry (@{$mesg->{entries}}) {
1882                            if($entry->exists('FAIrepository')) {
1883                                    # Add an entry for each Repository configured for server
1884                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1885                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1886                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1887                                                    $result= $fai_server_db->add_dbentry( { 
1888                                                                    table => $table_name,
1889                                                                    primkey => ['server', 'release', 'tag'],
1890                                                                    server => $tmp_url,
1891                                                                    release => $tmp_release,
1892                                                                    sections => $tmp_sections,
1893                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1894                                                            } );
1895                                            }
1896                                    }
1897                            }
1898                    }
1899                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1901                 # TODO: Find a way to post the 'create_packages_list_db' event
1902                 if(not defined($dont_create_packages_list)) {
1903                         &create_packages_list_db(undef, undef, $session_id);
1904                 }
1905         }       
1906     
1907     $ldap_handle->disconnect;
1908         return $result;
1912 sub run_create_fai_release_db {
1913     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1914         my $session_id = $session->ID;
1915     my $task = POE::Wheel::Run->new(
1916             Program => sub { &create_fai_release_db($table_name, $session_id) },
1917             StdoutEvent  => "session_run_result",
1918             StderrEvent  => "session_run_debug",
1919             CloseEvent   => "session_run_done",
1920             );
1922     $heap->{task}->{ $task->ID } = $task;
1923     return;
1927 sub create_fai_release_db {
1928         my ($table_name, $session_id) = @_;
1929         my $result;
1931     # used for logging
1932     if (not defined $session_id) { $session_id = 0; }
1934     my $ldap_handle = &get_ldap_handle();
1935         if(defined($ldap_handle)) {
1936                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1937                 my $mesg= $ldap_handle->search(
1938                         base   => $ldap_base,
1939                         scope  => 'sub',
1940                         attrs  => [],
1941                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1942                 );
1943                 if($mesg->{'resultCode'} == 0 &&
1944                         $mesg->count != 0) {
1945                         # Walk through all possible FAI container ou's
1946                         my @sql_list;
1947                         my $timestamp= &get_time();
1948                         foreach my $ou (@{$mesg->{entries}}) {
1949                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1950                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1951                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1952                                         if(@tmp_array) {
1953                                                 foreach my $entry (@tmp_array) {
1954                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1955                                                                 my $sql= 
1956                                                                 "INSERT INTO $table_name "
1957                                                                 ."(timestamp, release, class, type, state) VALUES ("
1958                                                                 .$timestamp.","
1959                                                                 ."'".$entry->{'release'}."',"
1960                                                                 ."'".$entry->{'class'}."',"
1961                                                                 ."'".$entry->{'type'}."',"
1962                                                                 ."'".$entry->{'state'}."')";
1963                                                                 push @sql_list, $sql;
1964                                                         }
1965                                                 }
1966                                         }
1967                                 }
1968                         }
1970                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1971                         if(@sql_list) {
1972                                 unshift @sql_list, "VACUUM";
1973                                 unshift @sql_list, "DELETE FROM $table_name";
1974                                 $fai_release_db->exec_statementlist(\@sql_list);
1975                         }
1976                         daemon_log("$session_id DEBUG: Done with inserting",7);
1977                 }
1978                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1979         }
1980     $ldap_handle->disconnect;
1981         return $result;
1984 sub get_fai_types {
1985         my $tmp_classes = shift || return undef;
1986         my @result;
1988         foreach my $type(keys %{$tmp_classes}) {
1989                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1990                         my $entry = {
1991                                 type => $type,
1992                                 state => $tmp_classes->{$type}[0],
1993                         };
1994                         push @result, $entry;
1995                 }
1996         }
1998         return @result;
2001 sub get_fai_state {
2002         my $result = "";
2003         my $tmp_classes = shift || return $result;
2005         foreach my $type(keys %{$tmp_classes}) {
2006                 if(defined($tmp_classes->{$type}[0])) {
2007                         $result = $tmp_classes->{$type}[0];
2008                         
2009                 # State is equal for all types in class
2010                         last;
2011                 }
2012         }
2014         return $result;
2017 sub resolve_fai_classes {
2018         my ($fai_base, $ldap_handle, $session_id) = @_;
2019         if (not defined $session_id) { $session_id = 0; }
2020         my $result;
2021         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2022         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2023         my $fai_classes;
2025         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2026         my $mesg= $ldap_handle->search(
2027                 base   => $fai_base,
2028                 scope  => 'sub',
2029                 attrs  => ['cn','objectClass','FAIstate'],
2030                 filter => $fai_filter,
2031         );
2032         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2034         if($mesg->{'resultCode'} == 0 &&
2035                 $mesg->count != 0) {
2036                 foreach my $entry (@{$mesg->{entries}}) {
2037                         if($entry->exists('cn')) {
2038                                 my $tmp_dn= $entry->dn();
2040                                 # Skip classname and ou dn parts for class
2041                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2043                                 # Skip classes without releases
2044                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2045                                         next;
2046                                 }
2048                                 my $tmp_cn= $entry->get_value('cn');
2049                                 my $tmp_state= $entry->get_value('FAIstate');
2051                                 my $tmp_type;
2052                                 # Get FAI type
2053                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2054                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2055                                                 $tmp_type= $oclass;
2056                                                 last;
2057                                         }
2058                                 }
2060                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2061                                         # A Subrelease
2062                                         my @sub_releases = split(/,/, $tmp_release);
2064                                         # Walk through subreleases and build hash tree
2065                                         my $hash;
2066                                         while(my $tmp_sub_release = pop @sub_releases) {
2067                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2068                                         }
2069                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2070                                 } else {
2071                                         # A branch, no subrelease
2072                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2073                                 }
2074                         } elsif (!$entry->exists('cn')) {
2075                                 my $tmp_dn= $entry->dn();
2076                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2078                                 # Skip classes without releases
2079                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2080                                         next;
2081                                 }
2083                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2084                                         # A Subrelease
2085                                         my @sub_releases= split(/,/, $tmp_release);
2087                                         # Walk through subreleases and build hash tree
2088                                         my $hash;
2089                                         while(my $tmp_sub_release = pop @sub_releases) {
2090                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2091                                         }
2092                                         # Remove the last two characters
2093                                         chop($hash);
2094                                         chop($hash);
2096                                         eval('$fai_classes->'.$hash.'= {}');
2097                                 } else {
2098                                         # A branch, no subrelease
2099                                         if(!exists($fai_classes->{$tmp_release})) {
2100                                                 $fai_classes->{$tmp_release} = {};
2101                                         }
2102                                 }
2103                         }
2104                 }
2106                 # The hash is complete, now we can honor the copy-on-write based missing entries
2107                 foreach my $release (keys %$fai_classes) {
2108                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2109                 }
2110         }
2111         return $result;
2114 sub apply_fai_inheritance {
2115        my $fai_classes = shift || return {};
2116        my $tmp_classes;
2118        # Get the classes from the branch
2119        foreach my $class (keys %{$fai_classes}) {
2120                # Skip subreleases
2121                if($class =~ /^ou=.*$/) {
2122                        next;
2123                } else {
2124                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2125                }
2126        }
2128        # Apply to each subrelease
2129        foreach my $subrelease (keys %{$fai_classes}) {
2130                if($subrelease =~ /ou=/) {
2131                        foreach my $tmp_class (keys %{$tmp_classes}) {
2132                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2133                                        $fai_classes->{$subrelease}->{$tmp_class} =
2134                                        deep_copy($tmp_classes->{$tmp_class});
2135                                } else {
2136                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2137                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2138                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2139                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2140                                                }
2141                                        }
2142                                }
2143                        }
2144                }
2145        }
2147        # Find subreleases in deeper levels
2148        foreach my $subrelease (keys %{$fai_classes}) {
2149                if($subrelease =~ /ou=/) {
2150                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2151                                if($subsubrelease =~ /ou=/) {
2152                                        apply_fai_inheritance($fai_classes->{$subrelease});
2153                                }
2154                        }
2155                }
2156        }
2158        return $fai_classes;
2161 sub get_fai_release_entries {
2162         my $tmp_classes = shift || return;
2163         my $parent = shift || "";
2164         my @result = shift || ();
2166         foreach my $entry (keys %{$tmp_classes}) {
2167                 if(defined($entry)) {
2168                         if($entry =~ /^ou=.*$/) {
2169                                 my $release_name = $entry;
2170                                 $release_name =~ s/ou=//g;
2171                                 if(length($parent)>0) {
2172                                         $release_name = $parent."/".$release_name;
2173                                 }
2174                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2175                                 foreach my $bufentry(@bufentries) {
2176                                         push @result, $bufentry;
2177                                 }
2178                         } else {
2179                                 my @types = get_fai_types($tmp_classes->{$entry});
2180                                 foreach my $type (@types) {
2181                                         push @result, 
2182                                         {
2183                                                 'class' => $entry,
2184                                                 'type' => $type->{'type'},
2185                                                 'release' => $parent,
2186                                                 'state' => $type->{'state'},
2187                                         };
2188                                 }
2189                         }
2190                 }
2191         }
2193         return @result;
2196 sub deep_copy {
2197         my $this = shift;
2198         if (not ref $this) {
2199                 $this;
2200         } elsif (ref $this eq "ARRAY") {
2201                 [map deep_copy($_), @$this];
2202         } elsif (ref $this eq "HASH") {
2203                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2204         } else { die "what type is $_?" }
2208 sub session_run_result {
2209     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2210     $kernel->sig(CHLD => "child_reap");
2213 sub session_run_debug {
2214     my $result = $_[ARG0];
2215     print STDERR "$result\n";
2218 sub session_run_done {
2219     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2220     delete $heap->{task}->{$task_id};
2224 sub create_sources_list {
2225         my $session_id = shift;
2226         my $ldap_handle = &main::get_ldap_handle;
2227         my $result="/tmp/gosa_si_tmp_sources_list";
2229         # Remove old file
2230         if(stat($result)) {
2231                 unlink($result);
2232                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2233         }
2235         my $fh;
2236         open($fh, ">$result");
2237         if (not defined $fh) {
2238                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2239                 return undef;
2240         }
2241         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2242                 my $mesg=$ldap_handle->search(
2243                         base    => $main::ldap_server_dn,
2244                         scope   => 'base',
2245                         attrs   => 'FAIrepository',
2246                         filter  => 'objectClass=FAIrepositoryServer'
2247                 );
2248                 if($mesg->count) {
2249                         foreach my $entry(@{$mesg->{'entries'}}) {
2250                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2251                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2252                                         my $line = "deb $server $release";
2253                                         $sections =~ s/,/ /g;
2254                                         $line.= " $sections";
2255                                         print $fh $line."\n";
2256                                 }
2257                         }
2258                 }
2259         } else {
2260                 if (defined $main::ldap_server_dn){
2261                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2262                 } else {
2263                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2264                 }
2265         }
2266         close($fh);
2268         return $result;
2272 sub run_create_packages_list_db {
2273     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2274         my $session_id = $session->ID;
2276         my $task = POE::Wheel::Run->new(
2277                                         Priority => +20,
2278                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2279                                         StdoutEvent  => "session_run_result",
2280                                         StderrEvent  => "session_run_debug",
2281                                         CloseEvent   => "session_run_done",
2282                                         );
2283         $heap->{task}->{ $task->ID } = $task;
2287 sub create_packages_list_db {
2288         my ($ldap_handle, $sources_file, $session_id) = @_;
2289         
2290         # it should not be possible to trigger a recreation of packages_list_db
2291         # while packages_list_db is under construction, so set flag packages_list_under_construction
2292         # which is tested befor recreation can be started
2293         if (-r $packages_list_under_construction) {
2294                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2295                 return;
2296         } else {
2297                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2298                 # set packages_list_under_construction to true
2299                 system("touch $packages_list_under_construction");
2300                 @packages_list_statements=();
2301         }
2303         if (not defined $session_id) { $session_id = 0; }
2304         if (not defined $ldap_handle) { 
2305                 $ldap_handle= &get_ldap_handle();
2307                 if (not defined $ldap_handle) {
2308                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2309                         unlink($packages_list_under_construction);
2310                         return;
2311                 }
2312         }
2313         if (not defined $sources_file) { 
2314                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2315                 $sources_file = &create_sources_list($session_id);
2316         }
2318         if (not defined $sources_file) {
2319                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2320                 unlink($packages_list_under_construction);
2321                 return;
2322         }
2324         my $line;
2326         open(CONFIG, "<$sources_file") or do {
2327                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2328                 unlink($packages_list_under_construction);
2329                 return;
2330         };
2332         # Read lines
2333         while ($line = <CONFIG>){
2334                 # Unify
2335                 chop($line);
2336                 $line =~ s/^\s+//;
2337                 $line =~ s/^\s+/ /;
2339                 # Strip comments
2340                 $line =~ s/#.*$//g;
2342                 # Skip empty lines
2343                 if ($line =~ /^\s*$/){
2344                         next;
2345                 }
2347                 # Interpret deb line
2348                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2349                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2350                         my $section;
2351                         foreach $section (split(' ', $sections)){
2352                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2353                         }
2354                 }
2355         }
2357         close (CONFIG);
2359         find(\&cleanup_and_extract, keys( %repo_dirs ));
2360         &main::strip_packages_list_statements();
2361         unshift @packages_list_statements, "VACUUM";
2362         $packages_list_db->exec_statementlist(\@packages_list_statements);
2363         unlink($packages_list_under_construction);
2364         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2365         return;
2368 # This function should do some intensive task to minimize the db-traffic
2369 sub strip_packages_list_statements {
2370     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2371         my @new_statement_list=();
2372         my $hash;
2373         my $insert_hash;
2374         my $update_hash;
2375         my $delete_hash;
2376         my $local_timestamp=get_time();
2378         foreach my $existing_entry (@existing_entries) {
2379                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2380         }
2382         foreach my $statement (@packages_list_statements) {
2383                 if($statement =~ /^INSERT/i) {
2384                         # Assign the values from the insert statement
2385                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2386                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2387                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2388                                 # If section or description has changed, update the DB
2389                                 if( 
2390                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2391                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2392                                 ) {
2393                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2394                                 }
2395                         } else {
2396                                 # Insert a non-existing entry to db
2397                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2398                         }
2399                 } elsif ($statement =~ /^UPDATE/i) {
2400                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2401                         /^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;
2402                         foreach my $distribution (keys %{$hash}) {
2403                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2404                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2405                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2406                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2407                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2408                                                 my $section;
2409                                                 my $description;
2410                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2411                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2412                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2413                                                 }
2414                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2415                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2416                                                 }
2417                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2418                                         }
2419                                 }
2420                         }
2421                 }
2422         }
2424         # TODO: Check for orphaned entries
2426         # unroll the insert_hash
2427         foreach my $distribution (keys %{$insert_hash}) {
2428                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2429                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2430                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2431                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2432                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2433                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2434                                 ."'$local_timestamp')";
2435                         }
2436                 }
2437         }
2439         # unroll the update hash
2440         foreach my $distribution (keys %{$update_hash}) {
2441                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2442                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2443                                 my $set = "";
2444                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2445                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2446                                 }
2447                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2448                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2449                                 }
2450                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2451                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2452                                 }
2453                                 if(defined($set) and length($set) > 0) {
2454                                         $set .= "timestamp = '$local_timestamp'";
2455                                 } else {
2456                                         next;
2457                                 }
2458                                 push @new_statement_list, 
2459                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2460                                         ." distribution = '$distribution'"
2461                                         ." AND package = '$package'"
2462                                         ." AND version = '$version'";
2463                         }
2464                 }
2465         }
2467         @packages_list_statements = @new_statement_list;
2471 sub parse_package_info {
2472     my ($baseurl, $dist, $section, $session_id)= @_;
2473     my ($package);
2474     if (not defined $session_id) { $session_id = 0; }
2475     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2476     $repo_dirs{ "${repo_path}/pool" } = 1;
2478     foreach $package ("Packages.gz"){
2479         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2480         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2481         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2482     }
2483     
2487 sub get_package {
2488     my ($url, $dest, $session_id)= @_;
2489     if (not defined $session_id) { $session_id = 0; }
2491     my $tpath = dirname($dest);
2492     -d "$tpath" || mkpath "$tpath";
2494     # This is ugly, but I've no time to take a look at "how it works in perl"
2495     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2496         system("gunzip -cd '$dest' > '$dest.in'");
2497         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2498         unlink($dest);
2499         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2500     } else {
2501         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2502     }
2503     return 0;
2507 sub parse_package {
2508     my ($path, $dist, $srv_path, $session_id)= @_;
2509     if (not defined $session_id) { $session_id = 0;}
2510     my ($package, $version, $section, $description);
2511     my $PACKAGES;
2512     my $timestamp = &get_time();
2514     if(not stat("$path.in")) {
2515         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2516         return;
2517     }
2519     open($PACKAGES, "<$path.in");
2520     if(not defined($PACKAGES)) {
2521         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2522         return;
2523     }
2525     # Read lines
2526     while (<$PACKAGES>){
2527         my $line = $_;
2528         # Unify
2529         chop($line);
2531         # Use empty lines as a trigger
2532         if ($line =~ /^\s*$/){
2533             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2534             push(@packages_list_statements, $sql);
2535             $package = "none";
2536             $version = "none";
2537             $section = "none";
2538             $description = "none"; 
2539             next;
2540         }
2542         # Trigger for package name
2543         if ($line =~ /^Package:\s/){
2544             ($package)= ($line =~ /^Package: (.*)$/);
2545             next;
2546         }
2548         # Trigger for version
2549         if ($line =~ /^Version:\s/){
2550             ($version)= ($line =~ /^Version: (.*)$/);
2551             next;
2552         }
2554         # Trigger for description
2555         if ($line =~ /^Description:\s/){
2556             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2557             next;
2558         }
2560         # Trigger for section
2561         if ($line =~ /^Section:\s/){
2562             ($section)= ($line =~ /^Section: (.*)$/);
2563             next;
2564         }
2566         # Trigger for filename
2567         if ($line =~ /^Filename:\s/){
2568             my ($filename) = ($line =~ /^Filename: (.*)$/);
2569             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2570             next;
2571         }
2572     }
2574     close( $PACKAGES );
2575     unlink( "$path.in" );
2576     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2580 sub store_fileinfo {
2581     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2583     my %fileinfo = (
2584         'package' => $package,
2585         'dist' => $dist,
2586         'version' => $vers,
2587     );
2589     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2593 sub cleanup_and_extract {
2594     my $fileinfo = $repo_files{ $File::Find::name };
2596     if( defined $fileinfo ) {
2598         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2599         my $sql;
2600         my $package = $fileinfo->{ 'package' };
2601         my $newver = $fileinfo->{ 'version' };
2603         mkpath($dir);
2604         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2606                 if( -f "$dir/DEBIAN/templates" ) {
2608                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2610                         my $tmpl= "";
2611                         {
2612                                 local $/=undef;
2613                                 open FILE, "$dir/DEBIAN/templates";
2614                                 $tmpl = &encode_base64(<FILE>);
2615                                 close FILE;
2616                         }
2617                         rmtree("$dir/DEBIAN/templates");
2619                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2620                 push @packages_list_statements, $sql;
2621                 }
2622     }
2624     return;
2628 sub register_at_foreign_servers {   
2629     my ($kernel) = $_[KERNEL];
2631     # hole alle bekannten server aus known_server_db
2632     my $server_sql = "SELECT * FROM $known_server_tn";
2633     my $server_res = $known_server_db->exec_statement($server_sql);
2635     # no entries in known_server_db
2636     if (not ref(@$server_res[0]) eq "ARRAY") { 
2637         # TODO
2638     }
2640     # detect already connected clients
2641     my $client_sql = "SELECT * FROM $known_clients_tn"; 
2642     my $client_res = $known_clients_db->exec_statement($client_sql);
2644     # send my server details to all other gosa-si-server within the network
2645     foreach my $hit (@$server_res) {
2646         my $hostname = @$hit[0];
2647         my $hostkey = &create_passwd;
2649         # add already connected clients to registration message 
2650         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2651         &add_content2xml_hash($myhash, 'key', $hostkey);
2652         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2653         
2654         # build registration message and send it
2655         my $foreign_server_msg = &create_xml_string($myhash);
2656         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2657     }
2658     
2659     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2660     return;
2664 #==== MAIN = main ==============================================================
2665 #  parse commandline options
2666 Getopt::Long::Configure( "bundling" );
2667 GetOptions("h|help" => \&usage,
2668         "c|config=s" => \$cfg_file,
2669         "f|foreground" => \$foreground,
2670         "v|verbose+" => \$verbose,
2671         "no-bus+" => \$no_bus,
2672         "no-arp+" => \$no_arp,
2673            );
2675 #  read and set config parameters
2676 &check_cmdline_param ;
2677 &read_configfile;
2678 &check_pid;
2680 $SIG{CHLD} = 'IGNORE';
2682 # forward error messages to logfile
2683 if( ! $foreground ) {
2684   open( STDIN,  '+>/dev/null' );
2685   open( STDOUT, '+>&STDIN'    );
2686   open( STDERR, '+>&STDIN'    );
2689 # Just fork, if we are not in foreground mode
2690 if( ! $foreground ) { 
2691     chdir '/'                 or die "Can't chdir to /: $!";
2692     $pid = fork;
2693     setsid                    or die "Can't start a new session: $!";
2694     umask 0;
2695 } else { 
2696     $pid = $$; 
2699 # Do something useful - put our PID into the pid_file
2700 if( 0 != $pid ) {
2701     open( LOCK_FILE, ">$pid_file" );
2702     print LOCK_FILE "$pid\n";
2703     close( LOCK_FILE );
2704     if( !$foreground ) { 
2705         exit( 0 ) 
2706     };
2709 # parse head url and revision from svn
2710 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2711 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2712 $server_headURL = defined $1 ? $1 : 'unknown' ;
2713 $server_revision = defined $2 ? $2 : 'unknown' ;
2714 if ($server_headURL =~ /\/tag\// || 
2715         $server_headURL =~ /\/branches\// ) {
2716     $server_status = "stable"; 
2717 } else {
2718     $server_status = "developmental" ;
2722 daemon_log(" ", 1);
2723 daemon_log("$0 started!", 1);
2724 daemon_log("status: $server_status", 1);
2725 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2727 if ($no_bus > 0) {
2728     $bus_activ = "false"
2731 # connect to incoming_db
2732 unlink($incoming_file_name);
2733 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2734 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2736 # connect to gosa-si job queue
2737 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2738 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2740 # connect to known_clients_db
2741 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2742 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2744 # connect to foreign_clients_db
2745 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2746 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2748 # connect to known_server_db
2749 unlink($known_server_file_name);
2750 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2751 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2753 # connect to login_usr_db
2754 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2755 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2757 # connect to fai_server_db and fai_release_db
2758 unlink($fai_server_file_name);
2759 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2760 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2762 unlink($fai_release_file_name);
2763 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2764 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2766 # connect to packages_list_db
2767 #unlink($packages_list_file_name);
2768 unlink($packages_list_under_construction);
2769 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2770 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2772 # connect to messaging_db
2773 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2774 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2777 # create xml object used for en/decrypting
2778 $xml = new XML::Simple();
2781 # foreign servers 
2782 my @foreign_server_list;
2784 # add foreign server from cfg file
2785 if ($foreign_server_string ne "") {
2786     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2787     foreach my $foreign_server (@cfg_foreign_server_list) {
2788         push(@foreign_server_list, $foreign_server);
2789     }
2792 # add foreign server from dns
2793 my @tmp_servers;
2794 if ( !$server_domain) {
2795     # Try our DNS Searchlist
2796     for my $domain(get_dns_domains()) {
2797         chomp($domain);
2798         my @tmp_domains= &get_server_addresses($domain);
2799         if(@tmp_domains) {
2800             for my $tmp_server(@tmp_domains) {
2801                 push @tmp_servers, $tmp_server;
2802             }
2803         }
2804     }
2805     if(@tmp_servers && length(@tmp_servers)==0) {
2806         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2807     }
2808 } else {
2809     @tmp_servers = &get_server_addresses($server_domain);
2810     if( 0 == @tmp_servers ) {
2811         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2812     }
2814 foreach my $server (@tmp_servers) { 
2815     unshift(@foreign_server_list, $server); 
2817 # eliminate duplicate entries
2818 @foreign_server_list = &del_doubles(@foreign_server_list);
2819 my $all_foreign_server = join(", ", @foreign_server_list);
2820 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2822 # add all found foreign servers to known_server
2823 my $act_timestamp = &get_time();
2824 foreach my $foreign_server (@foreign_server_list) {
2825     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2826             primkey=>['hostname'],
2827             hostname=>$foreign_server,
2828             status=>'not_jet_registered',
2829             hostkey=>"none",
2830             timestamp=>$act_timestamp,
2831             } );
2835 POE::Component::Server::TCP->new(
2836     Alias => "TCP_SERVER",
2837         Port => $server_port,
2838         ClientInput => sub {
2839         my ($kernel, $input) = @_[KERNEL, ARG0];
2840         push(@tasks, $input);
2841         push(@msgs_to_decrypt, $input);
2842         $kernel->yield("msg_to_decrypt");
2843         },
2844     InlineStates => {
2845         msg_to_decrypt => \&msg_to_decrypt,
2846         next_task => \&next_task,
2847         task_result => \&handle_task_result,
2848         task_done   => \&handle_task_done,
2849         task_debug  => \&handle_task_debug,
2850         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2851     }
2852 );
2854 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2856 # create session for repeatedly checking the job queue for jobs
2857 POE::Session->create(
2858         inline_states => {
2859                 _start => \&session_start,
2860         register_at_foreign_servers => \&register_at_foreign_servers,
2861         sig_handler => \&sig_handler,
2862         next_task => \&next_task,
2863         task_result => \&handle_task_result,
2864         task_done   => \&handle_task_done,
2865         task_debug  => \&handle_task_debug,
2866         watch_for_next_tasks => \&watch_for_next_tasks,
2867         watch_for_new_messages => \&watch_for_new_messages,
2868         watch_for_delivery_messages => \&watch_for_delivery_messages,
2869         watch_for_done_messages => \&watch_for_done_messages,
2870                 watch_for_new_jobs => \&watch_for_new_jobs,
2871         watch_for_done_jobs => \&watch_for_done_jobs,
2872         watch_for_old_known_clients => \&watch_for_old_known_clients,
2873         create_packages_list_db => \&run_create_packages_list_db,
2874         create_fai_server_db => \&run_create_fai_server_db,
2875         create_fai_release_db => \&run_create_fai_release_db,
2876         session_run_result => \&session_run_result,
2877         session_run_debug => \&session_run_debug,
2878         session_run_done => \&session_run_done,
2879         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2880         }
2881 );
2884 # import all modules
2885 &import_modules;
2887 # TODO
2888 # check wether all modules are gosa-si valid passwd check
2892 POE::Kernel->run();
2893 exit;