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
240 EOF
241 print "\n" ;
242 }
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 }
268 }
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 }
311 }
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 }
336 }
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 }
386 }
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 }
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);
431 }
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
449 daemon_log("shutting down gosa-si-server", 1);
450 system("kill `ps -C gosa-si-server -o pid=`");
451 }
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);
500 }
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 }
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);
563 }
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 }
601 return ($msg, $msg_hash, $module);
602 }
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);
639 }
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;
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);
679 }
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;
692 }
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;
709 }
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;
720 }
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;
757 }
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;
783 }
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;
812 }
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;
854 }
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);
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 }
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);
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;
947 }
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;
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 }
972 }
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");
982 }
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;
989 }
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 }
1008 }
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");
1027 }
1029 sub handle_task_debug {
1030 my $result = $_[ARG0];
1031 print STDERR "$result\n";
1032 }
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");
1038 }
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
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 }
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;
1244 }
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);
1253 }
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);
1269 }
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 }
1363 }
1366 sub watch_for_new_messages {
1367 my ($kernel,$heap) = @_[KERNEL, HEAP];
1368 my @coll_user_msg; # collection list of outgoing messages
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 }
1431 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1434 return;
1435 }
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;
1443 }
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;
1479 }
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 }
1566 }
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 }
1621 }
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;
1636 }
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 }
1681 $ldap_handle->disconnect;
1682 return $result;
1683 }
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;
1698 }
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;
1755 }
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;
1772 }
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];
1782 # State is equal for all types in class
1783 last;
1784 }
1785 }
1787 return $result;
1788 }
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;
1885 }
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;
1932 }
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;
1967 }
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 $_?" }
1978 }
1981 sub session_run_result {
1982 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1983 $kernel->sig(CHLD => "child_reap");
1984 }
1986 sub session_run_debug {
1987 my $result = $_[ARG0];
1988 print STDERR "$result\n";
1989 }
1991 sub session_run_done {
1992 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1993 delete $heap->{task}->{$task_id};
1994 }
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;
2037 }
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;
2051 }
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 };
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;
2126 }
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 }
2142 find(\&cleanup_and_extract, keys( %repo_dirs ));
2143 }
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;
2163 }
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);
2240 }
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;
2253 }
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;
2292 }
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' );
2318 }
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 = $$;
2328 }
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 };
2338 }
2340 daemon_log(" ", 1);
2341 daemon_log("$0 started!", 1);
2343 if ($no_bus > 0) {
2344 $bus_activ = "false"
2345 }
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;