Code

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