Code

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