Code

Fixed typo.
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5  qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
56 # TODO es gibt eine globale funktion get_ldap_handle
57 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
58 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
59 # - sessions die kein ldap handle brauchen, sollen auch keins haben
60 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
61 our $global_kernel;
63 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
64 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
65 my ($server);
66 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
67 my ($messaging_db_loop_delay);
68 my ($known_modules);
69 my ($pid_file, $procid, $pid, $log_file);
70 my ($arp_activ, $arp_fifo);
71 my ($xml);
72 my $sources_list;
73 my $max_clients;
74 my %repo_files=();
75 my $repo_path;
76 my %repo_dirs=();
77 # variables declared in config file are always set to 'our'
78 our (%cfg_defaults, $log_file, $pid_file, 
79     $server_ip, $server_port, $SIPackages_key, 
80     $arp_activ, $gosa_unit_tag,
81     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
82 );
84 # additional variable which should be globaly accessable
85 our $server_address;
86 our $server_mac_address;
87 our $bus_address;
88 our $gosa_address;
89 our $no_bus;
90 our $no_arp;
91 our $verbose;
92 our $forground;
93 our $cfg_file;
94 #our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
95 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
98 # specifies the verbosity of the daemon_log
99 $verbose = 0 ;
101 # if foreground is not null, script will be not forked to background
102 $foreground = 0 ;
104 # specifies the timeout seconds while checking the online status of a registrating client
105 $ping_timeout = 5;
107 $no_bus = 0;
108 $bus_activ = "true";
109 $no_arp = 0;
110 my $packages_list_under_construction = 0;
111 my $watch_for_new_jobs_in_progress = 0;
113 our $prg= basename($0);
115 # holds all gosa jobs
116 our $job_db;
117 our $job_queue_tn = 'jobs';
118 my $job_queue_file_name;
119 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
120                 "timestamp DEFAULT 'none'", 
121                 "status DEFAULT 'none'", 
122                 "result DEFAULT 'none'", 
123                 "progress DEFAULT 'none'", 
124         "headertag DEFAULT 'none'", 
125                 "targettag DEFAULT 'none'", 
126                 "xmlmessage DEFAULT 'none'", 
127                 "macaddress DEFAULT 'none'",
128                 "plainname DEFAULT 'none'",
129                 );
131 # holds all other gosa-sd as well as the gosa-sd-bus
132 our $known_server_db;
133 our $known_server_tn = "known_server";
134 my $known_server_file_name;
135 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
137 # holds all registrated clients
138 our $known_clients_db;
139 our $known_clients_tn = "known_clients";
140 my $known_clients_file_name;
141 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
143 # holds all logged in user at each client 
144 our $login_users_db;
145 our $login_users_tn = "login_users";
146 my $login_users_file_name;
147 my @login_users_col_names = ("client", "user", "timestamp");
149 # holds all fai server, the debian release and tag
150 our $fai_server_db;
151 our $fai_server_tn = "fai_server"; 
152 my $fai_server_file_name;
153 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
155 our $fai_release_db;
156 our $fai_release_tn = "fai_release"; 
157 my $fai_release_file_name;
158 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
160 # holds all packages available from different repositories
161 our $packages_list_db;
162 our $packages_list_tn = "packages_list";
163 my $packages_list_file_name;
164 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
165 my $outdir = "/tmp/packages_list_db";
166 my $arch = "i386"; 
168 # holds all messages which should be delivered to a user
169 our $messaging_db;
170 our $messaging_tn = "messaging"; 
171 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
172         "flag", "direction", "delivery_time", "message", "timestamp" );
173 my $messaging_file_name;
175 # path to directory to store client install log files
176 our $client_fai_log_dir = "/var/log/fai"; 
178 # queue which stores taskes until one of the $max_children children are ready to process the task
179 my @tasks = qw();
180 my $max_children = 2;
183 %cfg_defaults = (
184 "general" => {
185     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
186     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
187     },
188 "bus" => {
189     "activ" => [\$bus_activ, "true"],
190     },
191 "server" => {
192     "port" => [\$server_port, "20081"],
193     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
194     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
195     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
196     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
197     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
198     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
199     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
200     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
201     "repo-path" => [\$repo_path, '/srv/www/repository'],
202     "ldap-uri" => [\$ldap_uri, ""],
203     "ldap-base" => [\$ldap_base, ""],
204     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
205     "ldap-admin-password" => [\$ldap_admin_password, ""],
206     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
207     "max-clients" => [\$max_clients, 10],
208     },
209 "GOsaPackages" => {
210     "ip" => [\$gosa_ip, "0.0.0.0"],
211     "port" => [\$gosa_port, "20082"],
212     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
213     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
214     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
215     "key" => [\$GosaPackages_key, "none"],
216     },
217 "SIPackages" => {
218     "key" => [\$SIPackages_key, "none"],
219     },
220 );
223 #===  FUNCTION  ================================================================
224 #         NAME:  usage
225 #   PARAMETERS:  nothing
226 #      RETURNS:  nothing
227 #  DESCRIPTION:  print out usage text to STDERR
228 #===============================================================================
229 sub usage {
230     print STDERR << "EOF" ;
231 usage: $prg [-hvf] [-c config]
233            -h        : this (help) message
234            -c <file> : config file
235            -f        : foreground, process will not be forked to background
236            -v        : be verbose (multiple to increase verbosity)
237            -no-bus   : starts $prg without connection to bus
238            -no-arp   : starts $prg without connection to arp module
239  
240 EOF
241     print "\n" ;
245 #===  FUNCTION  ================================================================
246 #         NAME:  read_configfile
247 #   PARAMETERS:  cfg_file - string -
248 #      RETURNS:  nothing
249 #  DESCRIPTION:  read cfg_file and set variables
250 #===============================================================================
251 sub read_configfile {
252     my $cfg;
253     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
254         if( -r $cfg_file ) {
255             $cfg = Config::IniFiles->new( -file => $cfg_file );
256         } else {
257             print STDERR "Couldn't read config file!\n";
258         }
259     } else {
260         $cfg = Config::IniFiles->new() ;
261     }
262     foreach my $section (keys %cfg_defaults) {
263         foreach my $param (keys %{$cfg_defaults{ $section }}) {
264             my $pinfo = $cfg_defaults{ $section }{ $param };
265             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
266         }
267     }
271 #===  FUNCTION  ================================================================
272 #         NAME:  logging
273 #   PARAMETERS:  level - string - default 'info'
274 #                msg - string -
275 #                facility - string - default 'LOG_DAEMON'
276 #      RETURNS:  nothing
277 #  DESCRIPTION:  function for logging
278 #===============================================================================
279 sub daemon_log {
280     # log into log_file
281     my( $msg, $level ) = @_;
282     if(not defined $msg) { return }
283     if(not defined $level) { $level = 1 }
284     if(defined $log_file){
285         open(LOG_HANDLE, ">>$log_file");
286         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
287             print STDERR "cannot open $log_file: $!";
288             return }
289             chomp($msg);
290                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
291             if($level <= $verbose){
292                 my ($seconds, $minutes, $hours, $monthday, $month,
293                         $year, $weekday, $yearday, $sommertime) = localtime(time);
294                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
295                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
296                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
297                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
298                 $month = $monthnames[$month];
299                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
300                 $year+=1900;
301                 my $name = $prg;
303                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
304                 print LOG_HANDLE $log_msg;
305                 if( $foreground ) { 
306                     print STDERR $log_msg;
307                 }
308             }
309         close( LOG_HANDLE );
310     }
314 #===  FUNCTION  ================================================================
315 #         NAME:  check_cmdline_param
316 #   PARAMETERS:  nothing
317 #      RETURNS:  nothing
318 #  DESCRIPTION:  validates commandline parameter
319 #===============================================================================
320 sub check_cmdline_param () {
321     my $err_config;
322     my $err_counter = 0;
323         if(not defined($cfg_file)) {
324                 $cfg_file = "/etc/gosa-si/server.conf";
325                 if(! -r $cfg_file) {
326                         $err_config = "please specify a config file";
327                         $err_counter += 1;
328                 }
329     }
330     if( $err_counter > 0 ) {
331         &usage( "", 1 );
332         if( defined( $err_config)) { print STDERR "$err_config\n"}
333         print STDERR "\n";
334         exit( -1 );
335     }
339 #===  FUNCTION  ================================================================
340 #         NAME:  check_pid
341 #   PARAMETERS:  nothing
342 #      RETURNS:  nothing
343 #  DESCRIPTION:  handels pid processing
344 #===============================================================================
345 sub check_pid {
346     $pid = -1;
347     # Check, if we are already running
348     if( open(LOCK_FILE, "<$pid_file") ) {
349         $pid = <LOCK_FILE>;
350         if( defined $pid ) {
351             chomp( $pid );
352             if( -f "/proc/$pid/stat" ) {
353                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
354                 if( $stat ) {
355                                         daemon_log("ERROR: Already running",1);
356                     close( LOCK_FILE );
357                     exit -1;
358                 }
359             }
360         }
361         close( LOCK_FILE );
362         unlink( $pid_file );
363     }
365     # create a syslog msg if it is not to possible to open PID file
366     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
367         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
368         if (open(LOCK_FILE, '<', $pid_file)
369                 && ($pid = <LOCK_FILE>))
370         {
371             chomp($pid);
372             $msg .= "(PID $pid)\n";
373         } else {
374             $msg .= "(unable to read PID)\n";
375         }
376         if( ! ($foreground) ) {
377             openlog( $0, "cons,pid", "daemon" );
378             syslog( "warning", $msg );
379             closelog();
380         }
381         else {
382             print( STDERR " $msg " );
383         }
384         exit( -1 );
385     }
388 #===  FUNCTION  ================================================================
389 #         NAME:  import_modules
390 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
391 #                are stored
392 #      RETURNS:  nothing
393 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
394 #                state is on is imported by "require 'file';"
395 #===============================================================================
396 sub import_modules {
397     daemon_log(" ", 1);
399     if (not -e $modules_path) {
400         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
401     }
403     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
404     while (defined (my $file = readdir (DIR))) {
405         if (not $file =~ /(\S*?).pm$/) {
406             next;
407         }
408                 my $mod_name = $1;
410         if( $file =~ /ArpHandler.pm/ ) {
411             if( $no_arp > 0 ) {
412                 next;
413             }
414         }
415         
416         eval { require $file; };
417         if ($@) {
418             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
419             daemon_log("$@", 5);
420                 } else {
421                         my $info = eval($mod_name.'::get_module_info()');
422                         # Only load module if get_module_info() returns a non-null object
423                         if( $info ) {
424                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
425                                 $known_modules->{$mod_name} = $info;
426                                 daemon_log("INFO: module $mod_name loaded", 5);
427                         }
428                 }
429     }   
430     close (DIR);
434 #===  FUNCTION  ================================================================
435 #         NAME:  sig_int_handler
436 #   PARAMETERS:  signal - string - signal arose from system
437 #      RETURNS:  noting
438 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
439 #===============================================================================
440 sub sig_int_handler {
441     my ($signal) = @_;
443 #       if (defined($ldap_handle)) {
444 #               $ldap_handle->disconnect;
445 #       }
446     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
447     
449     daemon_log("shutting down gosa-si-server", 1);
450     system("kill `ps -C gosa-si-server -o pid=`");
452 $SIG{INT} = \&sig_int_handler;
455 sub check_key_and_xml_validity {
456     my ($crypted_msg, $module_key, $session_id) = @_;
457     my $msg;
458     my $msg_hash;
459     my $error_string;
460     eval{
461         $msg = &decrypt_msg($crypted_msg, $module_key);
463         if ($msg =~ /<xml>/i){
464             $msg =~ s/\s+/ /g;  # just for better daemon_log
465             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
466             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
468             ##############
469             # check header
470             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
471             my $header_l = $msg_hash->{'header'};
472             if( 1 > @{$header_l} ) { die 'empty header tag'; }
473             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
474             my $header = @{$header_l}[0];
475             if( 0 == length $header) { die 'empty string in header tag'; }
477             ##############
478             # check source
479             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
480             my $source_l = $msg_hash->{'source'};
481             if( 1 > @{$source_l} ) { die 'empty source tag'; }
482             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
483             my $source = @{$source_l}[0];
484             if( 0 == length $source) { die 'source error'; }
486             ##############
487             # check target
488             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
489             my $target_l = $msg_hash->{'target'};
490             if( 1 > @{$target_l} ) { die 'empty target tag'; }
491         }
492     };
493     if($@) {
494         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
495         $msg = undef;
496         $msg_hash = undef;
497     }
499     return ($msg, $msg_hash);
503 sub check_outgoing_xml_validity {
504     my ($msg) = @_;
506     my $msg_hash;
507     eval{
508         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
510         ##############
511         # check header
512         my $header_l = $msg_hash->{'header'};
513         if( 1 != @{$header_l} ) {
514             die 'no or more than one headers specified';
515         }
516         my $header = @{$header_l}[0];
517         if( 0 == length $header) {
518             die 'header has length 0';
519         }
521         ##############
522         # check source
523         my $source_l = $msg_hash->{'source'};
524         if( 1 != @{$source_l} ) {
525             die 'no or more than 1 sources specified';
526         }
527         my $source = @{$source_l}[0];
528         if( 0 == length $source) {
529             die 'source has length 0';
530         }
531         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
532                 $source =~ /^GOSA$/i ) {
533             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
534         }
535         
536         ##############
537         # check target  
538         my $target_l = $msg_hash->{'target'};
539         if( 0 == @{$target_l} ) {
540             die "no targets specified";
541         }
542         foreach my $target (@$target_l) {
543             if( 0 == length $target) {
544                 die "target has length 0";
545             }
546             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
547                     $target =~ /^GOSA$/i ||
548                     $target =~ /^\*$/ ||
549                     $target =~ /KNOWN_SERVER/i ||
550                     $target =~ /JOBDB/i ||
551                     $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 ){
552                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
553             }
554         }
555     };
556     if($@) {
557         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
558         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
559         $msg_hash = undef;
560     }
562     return ($msg_hash);
566 sub input_from_known_server {
567     my ($input, $remote_ip, $session_id) = @_ ;  
568     my ($msg, $msg_hash, $module);
570     my $sql_statement= "SELECT * FROM known_server";
571     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
573     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
574         my $host_name = $hit->{hostname};
575         if( not $host_name =~ "^$remote_ip") {
576             next;
577         }
578         my $host_key = $hit->{hostkey};
579         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
580         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
582         # check if module can open msg envelope with module key
583         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
584         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
585             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
586             daemon_log("$@", 8);
587             next;
588         }
589         else {
590             $msg = $tmp_msg;
591             $msg_hash = $tmp_msg_hash;
592             $module = "SIPackages";
593             last;
594         }
595     }
597     if( (!$msg) || (!$msg_hash) || (!$module) ) {
598         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
599     }
600   
601     return ($msg, $msg_hash, $module);
605 sub input_from_known_client {
606     my ($input, $remote_ip, $session_id) = @_ ;  
607     my ($msg, $msg_hash, $module);
609     my $sql_statement= "SELECT * FROM known_clients";
610     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
611     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
612         my $host_name = $hit->{hostname};
613         if( not $host_name =~ /^$remote_ip:\d*$/) {
614                 next;
615                 }
616         my $host_key = $hit->{hostkey};
617         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
618         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
620         # check if module can open msg envelope with module key
621         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
623         if( (!$msg) || (!$msg_hash) ) {
624             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
625             &daemon_log("$@", 8);
626             next;
627         }
628         else {
629             $module = "SIPackages";
630             last;
631         }
632     }
634     if( (!$msg) || (!$msg_hash) || (!$module) ) {
635         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
636     }
638     return ($msg, $msg_hash, $module);
642 sub input_from_unknown_host {
643     no strict "refs";
644     my ($input, $session_id) = @_ ;
645     my ($msg, $msg_hash, $module);
646     my $error_string;
647     
648         my %act_modules = %$known_modules;
650         while( my ($mod, $info) = each(%act_modules)) {
652         # check a key exists for this module
653         my $module_key = ${$mod."_key"};
654         if( not defined $module_key ) {
655             if( $mod eq 'ArpHandler' ) {
656                 next;
657             }
658             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
659             next;
660         }
661         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
663         # check if module can open msg envelope with module key
664         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
665         if( (not defined $msg) || (not defined $msg_hash) ) {
666             next;
667         }
668         else {
669             $module = $mod;
670             last;
671         }
672     }
674     if( (!$msg) || (!$msg_hash) || (!$module)) {
675         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
676     }
678     return ($msg, $msg_hash, $module);
682 sub create_ciphering {
683     my ($passwd) = @_;
684         if((!defined($passwd)) || length($passwd)==0) {
685                 $passwd = "";
686         }
687     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
688     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
689     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
690     $my_cipher->set_iv($iv);
691     return $my_cipher;
695 sub encrypt_msg {
696     my ($msg, $key) = @_;
697     my $my_cipher = &create_ciphering($key);
698     my $len;
699     {
700             use bytes;
701             $len= 16-length($msg)%16;
702     }
703     $msg = "\0"x($len).$msg;
704     $msg = $my_cipher->encrypt($msg);
705     chomp($msg = &encode_base64($msg));
706     # there are no newlines allowed inside msg
707     $msg=~ s/\n//g;
708     return $msg;
712 sub decrypt_msg {
714     my ($msg, $key) = @_ ;
715     $msg = &decode_base64($msg);
716     my $my_cipher = &create_ciphering($key);
717     $msg = $my_cipher->decrypt($msg); 
718     $msg =~ s/\0*//g;
719     return $msg;
723 sub get_encrypt_key {
724     my ($target) = @_ ;
725     my $encrypt_key;
726     my $error = 0;
728     # target can be in known_server
729     if( not defined $encrypt_key ) {
730         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
731         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
732         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
733             my $host_name = $hit->{hostname};
734             if( $host_name ne $target ) {
735                 next;
736             }
737             $encrypt_key = $hit->{hostkey};
738             last;
739         }
740     }
742     # target can be in known_client
743     if( not defined $encrypt_key ) {
744         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
745         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
746         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
747             my $host_name = $hit->{hostname};
748             if( $host_name ne $target ) {
749                 next;
750             }
751             $encrypt_key = $hit->{hostkey};
752             last;
753         }
754     }
756     return $encrypt_key;
760 #===  FUNCTION  ================================================================
761 #         NAME:  open_socket
762 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
763 #                [PeerPort] string necessary if port not appended by PeerAddr
764 #      RETURNS:  socket IO::Socket::INET
765 #  DESCRIPTION:  open a socket to PeerAddr
766 #===============================================================================
767 sub open_socket {
768     my ($PeerAddr, $PeerPort) = @_ ;
769     if(defined($PeerPort)){
770         $PeerAddr = $PeerAddr.":".$PeerPort;
771     }
772     my $socket;
773     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
774             Porto => "tcp",
775             Type => SOCK_STREAM,
776             Timeout => 5,
777             );
778     if(not defined $socket) {
779         return;
780     }
781 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
782     return $socket;
786 #===  FUNCTION  ================================================================
787 #         NAME:  get_ip 
788 #   PARAMETERS:  interface name (i.e. eth0)
789 #      RETURNS:  (ip address) 
790 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
791 #===============================================================================
792 sub get_ip {
793         my $ifreq= shift;
794         my $result= "";
795         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
796         my $proto= getprotobyname('ip');
798         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
799                 or die "socket: $!";
801         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
802                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
803                 my ($port, $addr) = sockaddr_in $sin;
804                 my $ip            = inet_ntoa $addr;
806                 if ($ip && length($ip) > 0) {
807                         $result = $ip;
808                 }
809         }
811         return $result;
815 sub get_local_ip_for_remote_ip {
816         my $remote_ip= shift;
817         my $result="0.0.0.0";
819         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
820                 if($remote_ip eq "127.0.0.1") {
821                         $result = "127.0.0.1";
822                 } else {
823                         my $PROC_NET_ROUTE= ('/proc/net/route');
825                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
826                                 or die "Could not open $PROC_NET_ROUTE";
828                         my @ifs = <PROC_NET_ROUTE>;
830                         close(PROC_NET_ROUTE);
832                         # Eat header line
833                         shift @ifs;
834                         chomp @ifs;
835                         foreach my $line(@ifs) {
836                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
837                                 my $destination;
838                                 my $mask;
839                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
840                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
841                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
842                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
843                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
844                                         # destination matches route, save mac and exit
845                                         $result= &get_ip($Iface);
846                                         last;
847                                 }
848                         }
849                 }
850         } else {
851                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
852         }
853         return $result;
857 sub send_msg_to_target {
858     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
859     my $error = 0;
860     my $header;
861     my $new_status;
862     my $act_status;
863     my ($sql_statement, $res);
864   
865     if( $msg_header ) {
866         $header = "'$msg_header'-";
867     } else {
868         $header = "";
869     }
871         # Patch the source ip
872         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
873                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
874                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
875         }
877     # encrypt xml msg
878     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
880     # opensocket
881     my $socket = &open_socket($address);
882     if( !$socket ) {
883         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
884         $error++;
885     }
886     
887     if( $error == 0 ) {
888         # send xml msg
889         print $socket $crypted_msg."\n";
891         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
892         #daemon_log("DEBUG: message:\n$msg", 9);
893         
894     }
896     # close socket in any case
897     if( $socket ) {
898         close $socket;
899     }
901     if( $error > 0 ) { $new_status = "down"; }
902     else { $new_status = $msg_header; }
905     # known_clients
906     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
907     $res = $known_clients_db->select_dbentry($sql_statement);
908     if( keys(%$res) > 0) {
909         $act_status = $res->{1}->{'status'};
910         if( $act_status eq "down" ) {
911             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
912             $res = $known_clients_db->del_dbentry($sql_statement);
913             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
914         } else { 
915             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
916             $res = $known_clients_db->update_dbentry($sql_statement);
917             if($new_status eq "down"){
918                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
919             } else {
920                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
921             }
922         }
923     }
925     # known_server
926     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
927     $res = $known_server_db->select_dbentry($sql_statement);
928     if( keys(%$res) > 0 ) {
929         $act_status = $res->{1}->{'status'};
930         if( $act_status eq "down" ) {
931             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
932             $res = $known_server_db->del_dbentry($sql_statement);
933             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
934         } 
935         else { 
936             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
937             $res = $known_server_db->update_dbentry($sql_statement);
938             if($new_status eq "down"){
939                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
940             }
941             else {
942                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
943             }
944         }
945     }
946     return $error; 
950 sub update_jobdb_status_for_send_msgs {
951     my ($answer, $error) = @_;
952     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
953         my $jobdb_id = $1;
954             
955         # sending msg faild
956         if( $error ) {
957             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
958                 my $sql_statement = "UPDATE $job_queue_tn ".
959                     "SET status='error', result='can not deliver msg, please consult log file' ".
960                     "WHERE id='$jobdb_id'";
961                 my $res = $job_db->update_dbentry($sql_statement);
962             }
964         # sending msg was successful
965         } else {
966             my $sql_statement = "UPDATE $job_queue_tn ".
967                 "SET status='done' ".
968                 "WHERE id='$jobdb_id' AND status='processed'";
969             my $res = $job_db->update_dbentry($sql_statement);
970         }
971     }
974 sub _start {
975     my ($kernel) = $_[KERNEL];
976     &trigger_db_loop($kernel);
977     $global_kernel = $kernel;
978         $kernel->yield('create_fai_server_db', $fai_server_tn );
979         $kernel->yield('create_fai_release_db', $fai_release_tn );
980         $kernel->sig(USR1 => "sig_handler");
981         $kernel->sig(USR2 => "create_packages_list_db");
984 sub sig_handler {
985         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
986         daemon_log("0 INFO got signal '$signal'", 1); 
987         $kernel->sig_handled();
988         return;
991 sub next_task {
992     my ($session, $heap) = @_[SESSION, HEAP];
994     while ( keys( %{ $heap->{task} } ) < $max_children ) {
995         my $next_task = shift @tasks;
996         last unless defined $next_task;
998         my $task = POE::Wheel::Run->new(
999                 Program => sub { process_task($session, $heap, $next_task) },
1000                 StdioFilter => POE::Filter::Reference->new(),
1001                 StdoutEvent  => "task_result",
1002                 StderrEvent  => "task_debug",
1003                 CloseEvent   => "task_done",
1004                );
1006         $heap->{task}->{ $task->ID } = $task;
1007     }
1010 sub handle_task_result {
1011     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1012     my $client_answer = $result->{'answer'};
1013     if( $client_answer =~ s/session_id=(\d+)$// ) {
1014         my $session_id = $1;
1015         if( defined $session_id ) {
1016             my $session_reference = $kernel->ID_id_to_session($session_id);
1017             if( defined $session_reference ) {
1018                 $heap = $session_reference->get_heap();
1019             }
1020         }
1022         if(exists $heap->{'client'}) {
1023             $heap->{'client'}->put($client_answer);
1024         }
1025     }
1026     $kernel->sig(CHLD => "child_reap");
1029 sub handle_task_debug {
1030     my $result = $_[ARG0];
1031     print STDERR "$result\n";
1034 sub handle_task_done {
1035     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1036     delete $heap->{task}->{$task_id};
1037     $kernel->yield("next_task");
1040 sub process_task {
1041     no strict "refs";
1042     my ($session, $heap, $input) = @_;
1043     my $session_id = $session->ID;
1044     my ($msg, $msg_hash, $module);
1045     my $error = 0;
1046     my $answer_l;
1047     my ($answer_header, @answer_target_l, $answer_source);
1048     my $client_answer = "";
1050     daemon_log("", 5); 
1051     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1052     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1054     ####################
1055     # check incoming msg
1056     # msg is from a new client or gosa
1057     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1058     # msg is from a gosa-si-server or gosa-si-bus
1059     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1060         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1061     }
1062     # msg is from a gosa-si-client
1063     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1064         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1065     }
1066     # an error occurred
1067     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1068         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1069         # could not understand a msg from its server the client cause a re-registering process
1070         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);
1071         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1072         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1073         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1074             my $host_name = $hit->{'hostname'};
1075             my $host_key = $hit->{'hostkey'};
1076             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1077             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1078             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1079         }
1080         $error++;
1081     }
1083     ######################
1084     # process incoming msg
1085     if( $error == 0) {
1086         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1087                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1088         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1089         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1091         if ( 0 < @{$answer_l} ) {
1092             my $answer_str = join("\n", @{$answer_l});
1093             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1094         } else {
1095             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1096         }
1098     }
1099     if( !$answer_l ) { $error++ };
1101     ########
1102     # answer
1103     if( $error == 0 ) {
1105         foreach my $answer ( @{$answer_l} ) {
1106             # for each answer in answer list
1107             
1108             # check outgoing msg to xml validity
1109             my $answer_hash = &check_outgoing_xml_validity($answer);
1110             if( not defined $answer_hash ) {
1111                 next;
1112             }
1113             
1114             $answer_header = @{$answer_hash->{'header'}}[0];
1115             @answer_target_l = @{$answer_hash->{'target'}};
1116             $answer_source = @{$answer_hash->{'source'}}[0];
1118             # deliver msg to all targets 
1119             foreach my $answer_target ( @answer_target_l ) {
1121                 # targets of msg are all gosa-si-clients in known_clients_db
1122                 if( $answer_target eq "*" ) {
1123                     # answer is for all clients
1124                     my $sql_statement= "SELECT * FROM known_clients";
1125                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1126                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1127                         my $host_name = $hit->{hostname};
1128                         my $host_key = $hit->{hostkey};
1129                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1130                         &update_jobdb_status_for_send_msgs($answer, $error);
1131                     }
1132                 }
1134                 # targets of msg are all gosa-si-server in known_server_db
1135                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1136                     # answer is for all server in known_server
1137                     my $sql_statement= "SELECT * FROM known_server";
1138                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1139                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1140                         my $host_name = $hit->{hostname};
1141                         my $host_key = $hit->{hostkey};
1142                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1143                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1144                         &update_jobdb_status_for_send_msgs($answer, $error);
1145                     }
1146                 }
1148                 # target of msg is GOsa
1149                                 elsif( $answer_target eq "GOSA" ) {
1150                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1151                                         my $add_on = "";
1152                     if( defined $session_id ) {
1153                         $add_on = ".session_id=$session_id";
1154                     }
1155                     # answer is for GOSA and has to returned to connected client
1156                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1157                     $client_answer = $gosa_answer.$add_on;
1158                 }
1160                 # target of msg is job queue at this host
1161                 elsif( $answer_target eq "JOBDB") {
1162                     $answer =~ /<header>(\S+)<\/header>/;   
1163                     my $header;
1164                     if( defined $1 ) { $header = $1; }
1165                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1166                     &update_jobdb_status_for_send_msgs($answer, $error);
1167                 }
1169                 # target of msg is a mac address
1170                 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 ) {
1171                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1172                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1173                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1174                     my $found_ip_flag = 0;
1175                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1176                         my $host_name = $hit->{hostname};
1177                         my $host_key = $hit->{hostkey};
1178                         $answer =~ s/$answer_target/$host_name/g;
1179                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1180                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1181                         &update_jobdb_status_for_send_msgs($answer, $error);
1182                         $found_ip_flag++ ;
1183                     }   
1184                     if( $found_ip_flag == 0) {
1185                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1186                         if( $bus_activ eq "true" ) { 
1187                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1188                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1189                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1190                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1191                                 my $bus_address = $hit->{hostname};
1192                                 my $bus_key = $hit->{hostkey};
1193                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1194                                 &update_jobdb_status_for_send_msgs($answer, $error);
1195                                 last;
1196                             }
1197                         }
1199                     }
1201                 #  answer is for one specific host   
1202                 } else {
1203                     # get encrypt_key
1204                     my $encrypt_key = &get_encrypt_key($answer_target);
1205                     if( not defined $encrypt_key ) {
1206                         # unknown target, forward msg to bus
1207                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1208                         if( $bus_activ eq "true" ) { 
1209                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1210                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1211                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1212                             my $res_length = keys( %{$query_res} );
1213                             if( $res_length == 0 ){
1214                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1215                                         "no bus found in known_server", 3);
1216                             }
1217                             else {
1218                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1219                                     my $bus_key = $hit->{hostkey};
1220                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1221                                     &update_jobdb_status_for_send_msgs($answer, $error);
1222                                 }
1223                             }
1224                         }
1225                         next;
1226                     }
1227                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1228                     &update_jobdb_status_for_send_msgs($answer, $error);
1229                 }
1230             }
1231         }
1232     }
1234     my $filter = POE::Filter::Reference->new();
1235     my %result = ( 
1236             status => "seems ok to me",
1237             answer => $client_answer,
1238             );
1240     my $output = $filter->put( [ \%result ] );
1241     print @$output;
1247 sub trigger_db_loop {
1248         my ($kernel) = @_ ;
1249         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1250         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1251     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1252     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1255 sub watch_for_done_jobs {
1256     my ($kernel,$heap) = @_[KERNEL, HEAP];
1258     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1259         " WHERE status='done'";
1260         my $res = $job_db->select_dbentry( $sql_statement );
1262     while( my ($id, $hit) = each %{$res} ) {
1263         my $jobdb_id = $hit->{id};
1264         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1265         my $res = $job_db->del_dbentry($sql_statement);
1266     }
1268     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1271 sub watch_for_new_jobs {
1272         if($watch_for_new_jobs_in_progress == 0) {
1273                 $watch_for_new_jobs_in_progress = 1;
1274                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1276                 # check gosa job queue for jobs with executable timestamp
1277                 my $timestamp = &get_time();
1278                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1279                 my $res = $job_db->exec_statement( $sql_statement );
1281                 # Merge all new jobs that would do the same actions
1282                 my @drops;
1283                 my $hits;
1284                 foreach my $hit (reverse @{$res} ) {
1285                         my $macaddress= lc @{$hit}[8];
1286                         my $headertag= @{$hit}[5];
1287                         if(
1288                                 defined($hits->{$macaddress}) &&
1289                                 defined($hits->{$macaddress}->{$headertag}) &&
1290                                 defined($hits->{$macaddress}->{$headertag}[0])
1291                         ) {
1292                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1293                         }
1294                         $hits->{$macaddress}->{$headertag}= $hit;
1295                 }
1297                 # Delete new jobs with a matching job in state 'processing'
1298                 foreach my $macaddress (keys %{$hits}) {
1299                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1300                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1301                                 if(defined($jobdb_id)) {
1302                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1303                                         my $res = $job_db->exec_statement( $sql_statement );
1304                                         foreach my $hit (@{$res}) {
1305                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1306                                         }
1307                                 } else {
1308                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1309                                 }
1310                         }
1311                 }
1313                 # Commit deletion
1314                 $job_db->exec_statementlist(\@drops);
1316                 # Look for new jobs that could be executed
1317                 foreach my $macaddress (keys %{$hits}) {
1319                         # Look if there is an executing job
1320                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1321                         my $res = $job_db->exec_statement( $sql_statement );
1323                         # Skip new jobs for host if there is a processing job
1324                         if(defined($res) and defined @{$res}[0]) {
1325                                 next;
1326                         }
1328                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1329                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1330                                 if(defined($jobdb_id)) {
1331                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1333                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1334                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1335                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1337                                         # expect macaddress is unique!!!!!!
1338                                         my $target = $res_hash->{1}->{hostname};
1340                                         # change header
1341                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1343                                         # add sqlite_id
1344                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1346                                         $job_msg =~ /<header>(\S+)<\/header>/;
1347                                         my $header = $1 ;
1348                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1350                                         # update status in job queue to 'processing'
1351                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1352                                         my $res = $job_db->update_dbentry($sql_statement);
1354                                         # We don't want parallel processing
1355                                         last;
1356                                 }
1357                         }
1358                 }
1360                 $watch_for_new_jobs_in_progress = 0;
1361                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1362         }
1366 sub watch_for_new_messages {
1367     my ($kernel,$heap) = @_[KERNEL, HEAP];
1368     my @coll_user_msg;   # collection list of outgoing messages
1369     
1370     # check messaging_db for new incoming messages with executable timestamp
1371     my $timestamp = &get_time();
1372     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1373     my $res = $messaging_db->exec_statement( $sql_statement );
1374         foreach my $hit (@{$res}) {
1376         # create outgoing messages
1377         my $message_to = @{$hit}[3];
1379         # translate message_to to plain login name
1380 # TODO implement reciever translation
1381         my @reciever_l = ($message_to);  
1382         my $message_id = @{$hit}[0];
1384         #add each outgoing msg to messaging_db
1385         my $reciever;
1386         foreach $reciever (@reciever_l) {
1387             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1388                 "VALUES ('".
1389                 $message_id."', '".    # id
1390                 @{$hit}[1]."', '".     # subject
1391                 @{$hit}[2]."', '".     # message_from
1392                 $reciever."', '".      # message_to
1393                 "none"."', '".         # flag
1394                 "out"."', '".          # direction
1395                 @{$hit}[6]."', '".     # delivery_time
1396                 @{$hit}[7]."', '".     # message
1397                 $timestamp."'".     # timestamp
1398                 ")";
1399             &daemon_log("M DEBUG: $sql_statement", 1);
1400             my $res = $messaging_db->exec_statement($sql_statement);
1401             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to reciever '$reciever'", 5);
1402         }
1404         # send outgoing messages
1405         my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1406         my $res = $messaging_db->exec_statement( $sql_statement );
1407         foreach my $hit (@{$res}) {
1408             # add subject, from, to and message to list coll_user_msg
1409             my @user_msg = [@{$hit}[1], @{$hit}[2], $reciever, @{$hit}[7]];
1410             push( @coll_user_msg, \@user_msg);
1411         }
1413         # send outgoing list to myself (gosa-si-server) to deliver each message to user
1414         # reason for this workaround: if to much messages have to be delivered, it can come to 
1415         # denial of service problems of the server. so, the incoming message list can be processed
1416         # by a forked child and gosa-si-server is always ready to work. 
1417         my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1418         # add to hash 'msg1' => [subject, from, to, message]
1419         # hash to string
1420         # send msg to myself
1421 # TODO
1423         # set incoming message to flag d=deliverd
1424         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1425         &daemon_log("M DEBUG: $sql_statement", 7);
1426         $res = $messaging_db->update_dbentry($sql_statement);
1427         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1429     }
1430     
1431     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1434     return;
1438 sub watch_for_done_messages {
1439     my ($kernel,$heap) = @_[KERNEL, HEAP];
1441     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1442     return;
1446 sub get_ldap_handle {
1447         my ($session_id) = @_;
1448         my $heap;
1449         my $ldap_handle;
1451         if (not defined $session_id ) { $session_id = 0 };
1452         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1454         if ($session_id == 0) {
1455                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1456                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1457                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1459         } else {
1460                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1461                 if( defined $session_reference ) {
1462                         $heap = $session_reference->get_heap();
1463                 }
1465                 if (not defined $heap) {
1466                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1467                         return;
1468                 }
1470                 # TODO: This "if" is nonsense, because it doesn't prove that the
1471                 #       used handle is still valid - or if we've to reconnect...
1472                 #if (not exists $heap->{ldap_handle}) {
1473                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1474                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1475                         $heap->{ldap_handle} = $ldap_handle;
1476                 #}
1477         }
1478         return $ldap_handle;
1482 sub change_fai_state {
1483     my ($st, $targets, $session_id) = @_;
1484     $session_id = 0 if not defined $session_id;
1485     # Set FAI state to localboot
1486     my %mapActions= (
1487         reboot    => '',
1488         update    => 'softupdate',
1489         localboot => 'localboot',
1490         reinstall => 'install',
1491         rescan    => '',
1492         wake      => '',
1493         memcheck  => 'memcheck',
1494         sysinfo   => 'sysinfo',
1495         install   => 'install',
1496     );
1498     # Return if this is unknown
1499     if (!exists $mapActions{ $st }){
1500         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1501       return;
1502     }
1504     my $state= $mapActions{ $st };
1506     my $ldap_handle = &get_ldap_handle($session_id);
1507     if( defined($ldap_handle) ) {
1509       # Build search filter for hosts
1510         my $search= "(&(objectClass=GOhard)";
1511         foreach (@{$targets}){
1512             $search.= "(macAddress=$_)";
1513         }
1514         $search.= ")";
1516       # If there's any host inside of the search string, procress them
1517         if (!($search =~ /macAddress/)){
1518             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1519             return;
1520         }
1522       # Perform search for Unit Tag
1523       my $mesg = $ldap_handle->search(
1524           base   => $ldap_base,
1525           scope  => 'sub',
1526           attrs  => ['dn', 'FAIstate', 'objectClass'],
1527           filter => "$search"
1528           );
1530           if ($mesg->count) {
1531                   my @entries = $mesg->entries;
1532                   foreach my $entry (@entries) {
1533                           # Only modify entry if it is not set to '$state'
1534                           if ($entry->get_value("FAIstate") ne "$state"){
1535                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1536                                   my $result;
1537                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1538                                   if (exists $tmp{'FAIobject'}){
1539                                           if ($state eq ''){
1540                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1541                                                           delete => [ FAIstate => [] ] ]);
1542                                           } else {
1543                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1544                                                           replace => [ FAIstate => $state ] ]);
1545                                           }
1546                                   } elsif ($state ne ''){
1547                                           $result= $ldap_handle->modify($entry->dn, changes => [
1548                                                   add     => [ objectClass => 'FAIobject' ],
1549                                                   add     => [ FAIstate => $state ] ]);
1550                                   }
1552                                   # Errors?
1553                                   if ($result->code){
1554                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1555                                   }
1556                           } else {
1557                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1558                           }  
1559                   }
1560           }
1561     # if no ldap handle defined
1562     } else {
1563         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1564     }
1569 sub change_goto_state {
1570     my ($st, $targets, $session_id) = @_;
1571     $session_id = 0  if not defined $session_id;
1573     # Switch on or off?
1574     my $state= $st eq 'active' ? 'active': 'locked';
1576     my $ldap_handle = &get_ldap_handle($session_id);
1577     if( defined($ldap_handle) ) {
1579       # Build search filter for hosts
1580       my $search= "(&(objectClass=GOhard)";
1581       foreach (@{$targets}){
1582         $search.= "(macAddress=$_)";
1583       }
1584       $search.= ")";
1586       # If there's any host inside of the search string, procress them
1587       if (!($search =~ /macAddress/)){
1588         return;
1589       }
1591       # Perform search for Unit Tag
1592       my $mesg = $ldap_handle->search(
1593           base   => $ldap_base,
1594           scope  => 'sub',
1595           attrs  => ['dn', 'gotoMode'],
1596           filter => "$search"
1597           );
1599       if ($mesg->count) {
1600         my @entries = $mesg->entries;
1601         foreach my $entry (@entries) {
1603           # Only modify entry if it is not set to '$state'
1604           if ($entry->get_value("gotoMode") ne $state){
1606             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1607             my $result;
1608             $result= $ldap_handle->modify($entry->dn, changes => [
1609                                                 replace => [ gotoMode => $state ] ]);
1611             # Errors?
1612             if ($result->code){
1613               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1614             }
1616           }
1617         }
1618       }
1620     }
1624 sub run_create_fai_server_db {
1625     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1626     my $session_id = $session->ID;
1627     my $task = POE::Wheel::Run->new(
1628             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1629             StdoutEvent  => "session_run_result",
1630             StderrEvent  => "session_run_debug",
1631             CloseEvent   => "session_run_done",
1632             );
1634     $heap->{task}->{ $task->ID } = $task;
1635     return;
1639 sub create_fai_server_db {
1640     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1641         my $result;
1643         if (not defined $session_id) { $session_id = 0; }
1644     my $ldap_handle = &get_ldap_handle();
1645         if(defined($ldap_handle)) {
1646                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1647                 my $mesg= $ldap_handle->search(
1648                         base   => $ldap_base,
1649                         scope  => 'sub',
1650                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1651                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1652                 );
1653                 if($mesg->{'resultCode'} == 0 &&
1654                    $mesg->count != 0) {
1655                    foreach my $entry (@{$mesg->{entries}}) {
1656                            if($entry->exists('FAIrepository')) {
1657                                    # Add an entry for each Repository configured for server
1658                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1659                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1660                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1661                                                    $result= $fai_server_db->add_dbentry( { 
1662                                                                    table => $table_name,
1663                                                                    primkey => ['server', 'release', 'tag'],
1664                                                                    server => $tmp_url,
1665                                                                    release => $tmp_release,
1666                                                                    sections => $tmp_sections,
1667                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1668                                                            } );
1669                                            }
1670                                    }
1671                            }
1672                    }
1673                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1675                 # TODO: Find a way to post the 'create_packages_list_db' event
1676                 if(not defined($dont_create_packages_list)) {
1677                         &create_packages_list_db(undef, undef, $session_id);
1678                 }
1679         }       
1680     
1681     $ldap_handle->disconnect;
1682         return $result;
1686 sub run_create_fai_release_db {
1687     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1688         my $session_id = $session->ID;
1689     my $task = POE::Wheel::Run->new(
1690             Program => sub { &create_fai_release_db($table_name, $session_id) },
1691             StdoutEvent  => "session_run_result",
1692             StderrEvent  => "session_run_debug",
1693             CloseEvent   => "session_run_done",
1694             );
1696     $heap->{task}->{ $task->ID } = $task;
1697     return;
1701 sub create_fai_release_db {
1702         my ($table_name, $session_id) = @_;
1703         my $result;
1705     # used for logging
1706     if (not defined $session_id) { $session_id = 0; }
1708     my $ldap_handle = &get_ldap_handle();
1709         if(defined($ldap_handle)) {
1710                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1711                 my $mesg= $ldap_handle->search(
1712                         base   => $ldap_base,
1713                         scope  => 'sub',
1714                         attrs  => [],
1715                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1716                 );
1717                 if($mesg->{'resultCode'} == 0 &&
1718                         $mesg->count != 0) {
1719                         # Walk through all possible FAI container ou's
1720                         my @sql_list;
1721                         my $timestamp= &get_time();
1722                         foreach my $ou (@{$mesg->{entries}}) {
1723                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1724                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1725                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1726                                         if(@tmp_array) {
1727                                                 foreach my $entry (@tmp_array) {
1728                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1729                                                                 my $sql= 
1730                                                                 "INSERT INTO $table_name "
1731                                                                 ."(timestamp, release, class, type, state) VALUES ("
1732                                                                 .$timestamp.","
1733                                                                 ."'".$entry->{'release'}."',"
1734                                                                 ."'".$entry->{'class'}."',"
1735                                                                 ."'".$entry->{'type'}."',"
1736                                                                 ."'".$entry->{'state'}."')";
1737                                                                 push @sql_list, $sql;
1738                                                         }
1739                                                 }
1740                                         }
1741                                 }
1742                         }
1744                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1745                         if(@sql_list) {
1746                                 unshift @sql_list, "DELETE FROM $table_name";   # at first, clear db
1747                                 $fai_release_db->exec_statementlist(\@sql_list);
1748                         }
1749                         daemon_log("$session_id DEBUG: Done with inserting",7);
1750                 }
1751                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1752         }
1753     $ldap_handle->disconnect;
1754         return $result;
1757 sub get_fai_types {
1758         my $tmp_classes = shift || return undef;
1759         my @result;
1761         foreach my $type(keys %{$tmp_classes}) {
1762                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1763                         my $entry = {
1764                                 type => $type,
1765                                 state => $tmp_classes->{$type}[0],
1766                         };
1767                         push @result, $entry;
1768                 }
1769         }
1771         return @result;
1774 sub get_fai_state {
1775         my $result = "";
1776         my $tmp_classes = shift || return $result;
1778         foreach my $type(keys %{$tmp_classes}) {
1779                 if(defined($tmp_classes->{$type}[0])) {
1780                         $result = $tmp_classes->{$type}[0];
1781                         
1782                 # State is equal for all types in class
1783                         last;
1784                 }
1785         }
1787         return $result;
1790 sub resolve_fai_classes {
1791         my ($fai_base, $ldap_handle, $session_id) = @_;
1792         if (not defined $session_id) { $session_id = 0; }
1793         my $result;
1794         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1795         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1796         my $fai_classes;
1798         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1799         my $mesg= $ldap_handle->search(
1800                 base   => $fai_base,
1801                 scope  => 'sub',
1802                 attrs  => ['cn','objectClass','FAIstate'],
1803                 filter => $fai_filter,
1804         );
1805         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1807         if($mesg->{'resultCode'} == 0 &&
1808                 $mesg->count != 0) {
1809                 foreach my $entry (@{$mesg->{entries}}) {
1810                         if($entry->exists('cn')) {
1811                                 my $tmp_dn= $entry->dn();
1813                                 # Skip classname and ou dn parts for class
1814                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1816                                 # Skip classes without releases
1817                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1818                                         next;
1819                                 }
1821                                 my $tmp_cn= $entry->get_value('cn');
1822                                 my $tmp_state= $entry->get_value('FAIstate');
1824                                 my $tmp_type;
1825                                 # Get FAI type
1826                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1827                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1828                                                 $tmp_type= $oclass;
1829                                                 last;
1830                                         }
1831                                 }
1833                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1834                                         # A Subrelease
1835                                         my @sub_releases = split(/,/, $tmp_release);
1837                                         # Walk through subreleases and build hash tree
1838                                         my $hash;
1839                                         while(my $tmp_sub_release = pop @sub_releases) {
1840                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1841                                         }
1842                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1843                                 } else {
1844                                         # A branch, no subrelease
1845                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1846                                 }
1847                         } elsif (!$entry->exists('cn')) {
1848                                 my $tmp_dn= $entry->dn();
1849                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1851                                 # Skip classes without releases
1852                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1853                                         next;
1854                                 }
1856                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1857                                         # A Subrelease
1858                                         my @sub_releases= split(/,/, $tmp_release);
1860                                         # Walk through subreleases and build hash tree
1861                                         my $hash;
1862                                         while(my $tmp_sub_release = pop @sub_releases) {
1863                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1864                                         }
1865                                         # Remove the last two characters
1866                                         chop($hash);
1867                                         chop($hash);
1869                                         eval('$fai_classes->'.$hash.'= {}');
1870                                 } else {
1871                                         # A branch, no subrelease
1872                                         if(!exists($fai_classes->{$tmp_release})) {
1873                                                 $fai_classes->{$tmp_release} = {};
1874                                         }
1875                                 }
1876                         }
1877                 }
1879                 # The hash is complete, now we can honor the copy-on-write based missing entries
1880                 foreach my $release (keys %$fai_classes) {
1881                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1882                 }
1883         }
1884         return $result;
1887 sub apply_fai_inheritance {
1888        my $fai_classes = shift || return {};
1889        my $tmp_classes;
1891        # Get the classes from the branch
1892        foreach my $class (keys %{$fai_classes}) {
1893                # Skip subreleases
1894                if($class =~ /^ou=.*$/) {
1895                        next;
1896                } else {
1897                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1898                }
1899        }
1901        # Apply to each subrelease
1902        foreach my $subrelease (keys %{$fai_classes}) {
1903                if($subrelease =~ /ou=/) {
1904                        foreach my $tmp_class (keys %{$tmp_classes}) {
1905                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1906                                        $fai_classes->{$subrelease}->{$tmp_class} =
1907                                        deep_copy($tmp_classes->{$tmp_class});
1908                                } else {
1909                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1910                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1911                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1912                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1913                                                }
1914                                        }
1915                                }
1916                        }
1917                }
1918        }
1920        # Find subreleases in deeper levels
1921        foreach my $subrelease (keys %{$fai_classes}) {
1922                if($subrelease =~ /ou=/) {
1923                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1924                                if($subsubrelease =~ /ou=/) {
1925                                        apply_fai_inheritance($fai_classes->{$subrelease});
1926                                }
1927                        }
1928                }
1929        }
1931        return $fai_classes;
1934 sub get_fai_release_entries {
1935         my $tmp_classes = shift || return;
1936         my $parent = shift || "";
1937         my @result = shift || ();
1939         foreach my $entry (keys %{$tmp_classes}) {
1940                 if(defined($entry)) {
1941                         if($entry =~ /^ou=.*$/) {
1942                                 my $release_name = $entry;
1943                                 $release_name =~ s/ou=//g;
1944                                 if(length($parent)>0) {
1945                                         $release_name = $parent."/".$release_name;
1946                                 }
1947                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1948                                 foreach my $bufentry(@bufentries) {
1949                                         push @result, $bufentry;
1950                                 }
1951                         } else {
1952                                 my @types = get_fai_types($tmp_classes->{$entry});
1953                                 foreach my $type (@types) {
1954                                         push @result, 
1955                                         {
1956                                                 'class' => $entry,
1957                                                 'type' => $type->{'type'},
1958                                                 'release' => $parent,
1959                                                 'state' => $type->{'state'},
1960                                         };
1961                                 }
1962                         }
1963                 }
1964         }
1966         return @result;
1969 sub deep_copy {
1970         my $this = shift;
1971         if (not ref $this) {
1972                 $this;
1973         } elsif (ref $this eq "ARRAY") {
1974                 [map deep_copy($_), @$this];
1975         } elsif (ref $this eq "HASH") {
1976                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1977         } else { die "what type is $_?" }
1981 sub session_run_result {
1982     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1983     $kernel->sig(CHLD => "child_reap");
1986 sub session_run_debug {
1987     my $result = $_[ARG0];
1988     print STDERR "$result\n";
1991 sub session_run_done {
1992     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1993     delete $heap->{task}->{$task_id};
1996 sub create_sources_list {
1997     my ($session_id) = @_;
1998     my $ldap_handle = &get_ldap_handle;
1999         my $result="/tmp/gosa_si_tmp_sources_list";
2001         # Remove old file
2002         if(stat($result)) {
2003                 unlink($result);
2004         &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2005         }
2007         my $fh;
2008         open($fh, ">$result");
2009     if (not defined $fh) {
2010         &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2011         return undef;
2012     }
2013         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
2014                 my $mesg=$ldap_handle->search(
2015                                 base    => $ldap_server_dn,
2016                                 scope   => 'base',
2017                                 attrs   => 'FAIrepository',
2018                                 filter  => 'objectClass=FAIrepositoryServer'
2019                                 );
2020                 if($mesg->count) {
2021                         foreach my $entry(@{$mesg->{'entries'}}) {
2022                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2023                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2024                                         my $line = "deb $server $release";
2025                                         $sections =~ s/,/ /g;
2026                                         $line.= " $sections";
2027                                         print $fh $line."\n";
2028                                 }
2029                         }
2030                 }
2031         } else {
2032         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$ldap_server_dn', abort create_sources_list", 1); 
2033     }
2034         close($fh);
2036         return $result;
2040 sub run_create_packages_list_db {
2041     my ($session, $heap) = @_[SESSION, HEAP];
2042         my $session_id = $session->ID;
2044         my $task = POE::Wheel::Run->new(
2045                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2046                                         StdoutEvent  => "session_run_result",
2047                                         StderrEvent  => "session_run_debug",
2048                                         CloseEvent   => "session_run_done",
2049                                         );
2050         $heap->{task}->{ $task->ID } = $task;
2054 sub create_packages_list_db {
2055     my ($ldap_handle, $sources_file, $session_id) = @_;
2057         if (not defined $session_id) { $session_id = 0; }
2058         if (not defined $ldap_handle) { 
2059                 $ldap_handle= &get_ldap_handle();
2061                 if (not defined $ldap_handle) {
2062                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2063                         return;
2064                 }
2065         }
2066     if (not defined $sources_file) { 
2067         &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2068         $sources_file = &create_sources_list($session_id);
2069     }
2071     if (not defined $sources_file) {
2072         &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2074     }
2076         # it should not be possible to trigger a recreation of packages_list_db
2077         # while packages_list_db is under construction, so set flag packages_list_under_construction
2078         # which is tested befor recreation can be started
2079         if ($packages_list_under_construction) {
2080                         daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait untill this process is finished", 3);
2081                         return;
2082         } else {
2083                         daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2084                         # set packages_list_under_construction to true
2085                         $packages_list_under_construction = 1;
2086         }
2087         my $line;
2089     open(CONFIG, "<$sources_file") or do {
2090         daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2091         return;
2092     };
2093     
2094     # Read lines
2095     while ($line = <CONFIG>){
2096         # Unify
2097         chop($line);
2098         $line =~ s/^\s+//;
2099         $line =~ s/^\s+/ /;
2101         # Strip comments
2102         $line =~ s/#.*$//g;
2104         # Skip empty lines
2105         if ($line =~ /^\s*$/){
2106             next;
2107         }
2109         # Interpret deb line
2110         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2111             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2112             my $section;
2113             foreach $section (split(' ', $sections)){
2114                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2115             }
2116         }
2117     }
2119     close (CONFIG);
2121     daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2122         # set packages_list_under_construction to false
2123         $packages_list_under_construction = 0;
2125     return;
2129 sub parse_package_info {
2130     my ($baseurl, $dist, $section, $session_id)= @_;
2131     my ($package);
2132     if (not defined $session_id) { $session_id = 0; }
2133     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2134     $repo_dirs{ "${repo_path}/pool" } = 1;
2136     foreach $package ("Packages.gz"){
2137         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2138         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2139         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2140     }
2141     
2142     find(\&cleanup_and_extract, keys( %repo_dirs ));
2146 sub get_package {
2147     my ($url, $dest, $session_id)= @_;
2148     if (not defined $session_id) { $session_id = 0; }
2150     my $tpath = dirname($dest);
2151     -d "$tpath" || mkpath "$tpath";
2153     # This is ugly, but I've no time to take a look at "how it works in perl"
2154     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2155         system("gunzip -cd '$dest' > '$dest.in'");
2156         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2157         unlink($dest);
2158         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2159     } else {
2160         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2161     }
2162     return 0;
2166 sub parse_package {
2167     my ($path, $dist, $srv_path, $session_id)= @_;
2168     if (not defined $session_id) { $session_id = 0;}
2169     my ($package, $version, $section, $description);
2170     my @sql_list;
2171     my $PACKAGES;
2172     my $timestamp = &get_time();
2174     if(not stat("$path.in")) {
2175         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2176         return;
2177     }
2179     open($PACKAGES, "<$path.in");
2180     if(not defined($PACKAGES)) {
2181         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2182         return;
2183     }
2185     # Read lines
2186     while (<$PACKAGES>){
2187         my $line = $_;
2188         # Unify
2189         chop($line);
2191         # Use empty lines as a trigger
2192         if ($line =~ /^\s*$/){
2193             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '$timestamp')";
2194             push(@sql_list, $sql);
2195             $package = "none";
2196             $version = "none";
2197             $section = "none";
2198             $description = "none"; 
2199             next;
2200         }
2202         # Trigger for package name
2203         if ($line =~ /^Package:\s/){
2204             ($package)= ($line =~ /^Package: (.*)$/);
2205             next;
2206         }
2208         # Trigger for version
2209         if ($line =~ /^Version:\s/){
2210             ($version)= ($line =~ /^Version: (.*)$/);
2211             next;
2212         }
2214         # Trigger for description
2215         if ($line =~ /^Description:\s/){
2216             ($description)= ($line =~ /^Description: (.*)$/);
2217             next;
2218         }
2220         # Trigger for section
2221         if ($line =~ /^Section:\s/){
2222             ($section)= ($line =~ /^Section: (.*)$/);
2223             next;
2224         }
2226         # Trigger for filename
2227         if ($line =~ /^Filename:\s/){
2228             my ($filename) = ($line =~ /^Filename: (.*)$/);
2229             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2230             next;
2231         }
2232     }
2234     close( $PACKAGES );
2235     unlink( "$path.in" );
2236     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2237     my $len_sql_list = @sql_list;
2238     &main::daemon_log("$session_id DEBUG: add $len_sql_list insert-statements to packages_list_db", 5); 
2239     $packages_list_db->exec_statementlist(\@sql_list);
2243 sub store_fileinfo {
2244     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2246     my %fileinfo = (
2247         'package' => $package,
2248         'dist' => $dist,
2249         'version' => $vers,
2250     );
2252     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2256 sub cleanup_and_extract {
2257     my $fileinfo = $repo_files{ $File::Find::name };
2259     if( defined $fileinfo ) {
2261         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2262         my $sql;
2263         my $package = $fileinfo->{ 'package' };
2264         my $newver = $fileinfo->{ 'version' };
2266         mkpath($dir);
2267         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2269         if( -f "$dir/DEBIAN/templates" ) {
2271             daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2273             my $tmpl= "";
2274             {
2275                 local $/=undef;
2276                 open FILE, "$dir/DEBIAN/templates";
2277                 $tmpl = &encode_base64(<FILE>);
2278                 close FILE;
2279             }
2280             rmtree("$dir/DEBIAN/templates");
2282             $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2284         } else {
2285             $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2286         }
2288         my $res= $main::packages_list_db->update_dbentry($sql);
2289     }
2291     return;
2295 #==== MAIN = main ==============================================================
2296 #  parse commandline options
2297 Getopt::Long::Configure( "bundling" );
2298 GetOptions("h|help" => \&usage,
2299         "c|config=s" => \$cfg_file,
2300         "f|foreground" => \$foreground,
2301         "v|verbose+" => \$verbose,
2302         "no-bus+" => \$no_bus,
2303         "no-arp+" => \$no_arp,
2304            );
2306 #  read and set config parameters
2307 &check_cmdline_param ;
2308 &read_configfile;
2309 &check_pid;
2311 $SIG{CHLD} = 'IGNORE';
2313 # forward error messages to logfile
2314 if( ! $foreground ) {
2315   open( STDIN,  '+>/dev/null' );
2316   open( STDOUT, '+>&STDIN'    );
2317   open( STDERR, '+>&STDIN'    );
2320 # Just fork, if we are not in foreground mode
2321 if( ! $foreground ) { 
2322     chdir '/'                 or die "Can't chdir to /: $!";
2323     $pid = fork;
2324     setsid                    or die "Can't start a new session: $!";
2325     umask 0;
2326 } else { 
2327     $pid = $$; 
2330 # Do something useful - put our PID into the pid_file
2331 if( 0 != $pid ) {
2332     open( LOCK_FILE, ">$pid_file" );
2333     print LOCK_FILE "$pid\n";
2334     close( LOCK_FILE );
2335     if( !$foreground ) { 
2336         exit( 0 ) 
2337     };
2340 daemon_log(" ", 1);
2341 daemon_log("$0 started!", 1);
2343 if ($no_bus > 0) {
2344     $bus_activ = "false"
2347 # connect to gosa-si job queue
2348 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2349 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2351 # connect to known_clients_db
2352 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2353 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2355 # connect to known_server_db
2356 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2357 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2359 # connect to login_usr_db
2360 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2361 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2363 # connect to fai_server_db and fai_release_db
2364 unlink($fai_server_file_name);
2365 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2366 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2368 unlink($fai_release_file_name);
2369 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2370 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2372 # connect to packages_list_db
2373 unlink($packages_list_file_name);
2374 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2375 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2377 # connect to messaging_db
2378 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2379 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2382 # create xml object used for en/decrypting
2383 $xml = new XML::Simple();
2385 # create socket for incoming xml messages
2387 POE::Component::Server::TCP->new(
2388         Port => $server_port,
2389         ClientInput => sub {
2390         my ($kernel, $input) = @_[KERNEL, ARG0];
2391         push(@tasks, $input);
2392         $kernel->yield("next_task");
2393         },
2394     InlineStates => {
2395         next_task => \&next_task,
2396         task_result => \&handle_task_result,
2397         task_done   => \&handle_task_done,
2398         task_debug  => \&handle_task_debug,
2399         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2400     }
2401 );
2403 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2405 # create session for repeatedly checking the job queue for jobs
2406 POE::Session->create(
2407         inline_states => {
2408                 _start => \&_start,
2409                 sig_handler => \&sig_handler,
2410         watch_for_new_messages => \&watch_for_new_messages,
2411         watch_for_done_messages => \&watch_for_done_messages,
2412                 watch_for_new_jobs => \&watch_for_new_jobs,
2413         watch_for_done_jobs => \&watch_for_done_jobs,
2414         create_packages_list_db => \&run_create_packages_list_db,
2415         create_fai_server_db => \&run_create_fai_server_db,
2416         create_fai_release_db => \&run_create_fai_release_db,
2417         session_run_result => \&session_run_result,
2418         session_run_debug => \&session_run_debug,
2419         session_run_done => \&session_run_done,
2420         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2421         }
2422 );
2425 # import all modules
2426 &import_modules;
2428 # check wether all modules are gosa-si valid passwd check
2430 POE::Kernel->run();
2431 exit;