0f3b33c9e8a1d184cf111b9d48064b50ef0687e0
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;
112 our $prg= basename($0);
114 # holds all gosa jobs
115 our $job_db;
116 our $job_queue_tn = 'jobs';
117 my $job_queue_file_name;
118 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
119 "timestamp DEFAULT 'none'",
120 "status DEFAULT 'none'",
121 "result DEFAULT 'none'",
122 "progress DEFAULT 'none'",
123 "headertag DEFAULT 'none'",
124 "targettag DEFAULT 'none'",
125 "xmlmessage DEFAULT 'none'",
126 "macaddress DEFAULT 'none'",
127 "plainname DEFAULT 'none'",
128 );
130 # holds all other gosa-sd as well as the gosa-sd-bus
131 our $known_server_db;
132 our $known_server_tn = "known_server";
133 my $known_server_file_name;
134 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
136 # holds all registrated clients
137 our $known_clients_db;
138 our $known_clients_tn = "known_clients";
139 my $known_clients_file_name;
140 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
142 # holds all logged in user at each client
143 our $login_users_db;
144 our $login_users_tn = "login_users";
145 my $login_users_file_name;
146 my @login_users_col_names = ("client", "user", "timestamp");
148 # holds all fai server, the debian release and tag
149 our $fai_server_db;
150 our $fai_server_tn = "fai_server";
151 my $fai_server_file_name;
152 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
154 our $fai_release_db;
155 our $fai_release_tn = "fai_release";
156 my $fai_release_file_name;
157 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
159 # holds all packages available from different repositories
160 our $packages_list_db;
161 our $packages_list_tn = "packages_list";
162 my $packages_list_file_name;
163 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
164 my $outdir = "/tmp/packages_list_db";
165 my $arch = "i386";
167 # holds all messages which should be delivered to a user
168 our $messaging_db;
169 our $messaging_tn = "messaging";
170 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
171 "flag", "direction", "delivery_time", "message", "timestamp" );
172 my $messaging_file_name;
174 # path to directory to store client install log files
175 our $client_fai_log_dir = "/var/log/fai";
177 # queue which stores taskes until one of the $max_children children are ready to process the task
178 my @tasks = qw();
179 my $max_children = 2;
182 %cfg_defaults = (
183 "general" => {
184 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
185 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
186 },
187 "bus" => {
188 "activ" => [\$bus_activ, "true"],
189 },
190 "server" => {
191 "port" => [\$server_port, "20081"],
192 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
193 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
194 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
195 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
196 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
197 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
198 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
199 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
200 "repo-path" => [\$repo_path, '/srv/www/repository'],
201 "ldap-uri" => [\$ldap_uri, ""],
202 "ldap-base" => [\$ldap_base, ""],
203 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
204 "ldap-admin-password" => [\$ldap_admin_password, ""],
205 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
206 "max-clients" => [\$max_clients, 10],
207 },
208 "GOsaPackages" => {
209 "ip" => [\$gosa_ip, "0.0.0.0"],
210 "port" => [\$gosa_port, "20082"],
211 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
212 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
213 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
214 "key" => [\$GosaPackages_key, "none"],
215 },
216 "SIPackages" => {
217 "key" => [\$SIPackages_key, "none"],
218 },
219 );
222 #=== FUNCTION ================================================================
223 # NAME: usage
224 # PARAMETERS: nothing
225 # RETURNS: nothing
226 # DESCRIPTION: print out usage text to STDERR
227 #===============================================================================
228 sub usage {
229 print STDERR << "EOF" ;
230 usage: $prg [-hvf] [-c config]
232 -h : this (help) message
233 -c <file> : config file
234 -f : foreground, process will not be forked to background
235 -v : be verbose (multiple to increase verbosity)
236 -no-bus : starts $prg without connection to bus
237 -no-arp : starts $prg without connection to arp module
239 EOF
240 print "\n" ;
241 }
244 #=== FUNCTION ================================================================
245 # NAME: read_configfile
246 # PARAMETERS: cfg_file - string -
247 # RETURNS: nothing
248 # DESCRIPTION: read cfg_file and set variables
249 #===============================================================================
250 sub read_configfile {
251 my $cfg;
252 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
253 if( -r $cfg_file ) {
254 $cfg = Config::IniFiles->new( -file => $cfg_file );
255 } else {
256 print STDERR "Couldn't read config file!\n";
257 }
258 } else {
259 $cfg = Config::IniFiles->new() ;
260 }
261 foreach my $section (keys %cfg_defaults) {
262 foreach my $param (keys %{$cfg_defaults{ $section }}) {
263 my $pinfo = $cfg_defaults{ $section }{ $param };
264 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
265 }
266 }
267 }
270 #=== FUNCTION ================================================================
271 # NAME: logging
272 # PARAMETERS: level - string - default 'info'
273 # msg - string -
274 # facility - string - default 'LOG_DAEMON'
275 # RETURNS: nothing
276 # DESCRIPTION: function for logging
277 #===============================================================================
278 sub daemon_log {
279 # log into log_file
280 my( $msg, $level ) = @_;
281 if(not defined $msg) { return }
282 if(not defined $level) { $level = 1 }
283 if(defined $log_file){
284 open(LOG_HANDLE, ">>$log_file");
285 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
286 print STDERR "cannot open $log_file: $!";
287 return }
288 chomp($msg);
289 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
290 if($level <= $verbose){
291 my ($seconds, $minutes, $hours, $monthday, $month,
292 $year, $weekday, $yearday, $sommertime) = localtime(time);
293 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
294 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
295 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
296 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
297 $month = $monthnames[$month];
298 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
299 $year+=1900;
300 my $name = $prg;
302 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
303 print LOG_HANDLE $log_msg;
304 if( $foreground ) {
305 print STDERR $log_msg;
306 }
307 }
308 close( LOG_HANDLE );
309 }
310 }
313 #=== FUNCTION ================================================================
314 # NAME: check_cmdline_param
315 # PARAMETERS: nothing
316 # RETURNS: nothing
317 # DESCRIPTION: validates commandline parameter
318 #===============================================================================
319 sub check_cmdline_param () {
320 my $err_config;
321 my $err_counter = 0;
322 if(not defined($cfg_file)) {
323 $cfg_file = "/etc/gosa-si/server.conf";
324 if(! -r $cfg_file) {
325 $err_config = "please specify a config file";
326 $err_counter += 1;
327 }
328 }
329 if( $err_counter > 0 ) {
330 &usage( "", 1 );
331 if( defined( $err_config)) { print STDERR "$err_config\n"}
332 print STDERR "\n";
333 exit( -1 );
334 }
335 }
338 #=== FUNCTION ================================================================
339 # NAME: check_pid
340 # PARAMETERS: nothing
341 # RETURNS: nothing
342 # DESCRIPTION: handels pid processing
343 #===============================================================================
344 sub check_pid {
345 $pid = -1;
346 # Check, if we are already running
347 if( open(LOCK_FILE, "<$pid_file") ) {
348 $pid = <LOCK_FILE>;
349 if( defined $pid ) {
350 chomp( $pid );
351 if( -f "/proc/$pid/stat" ) {
352 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
353 if( $stat ) {
354 daemon_log("ERROR: Already running",1);
355 close( LOCK_FILE );
356 exit -1;
357 }
358 }
359 }
360 close( LOCK_FILE );
361 unlink( $pid_file );
362 }
364 # create a syslog msg if it is not to possible to open PID file
365 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
366 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
367 if (open(LOCK_FILE, '<', $pid_file)
368 && ($pid = <LOCK_FILE>))
369 {
370 chomp($pid);
371 $msg .= "(PID $pid)\n";
372 } else {
373 $msg .= "(unable to read PID)\n";
374 }
375 if( ! ($foreground) ) {
376 openlog( $0, "cons,pid", "daemon" );
377 syslog( "warning", $msg );
378 closelog();
379 }
380 else {
381 print( STDERR " $msg " );
382 }
383 exit( -1 );
384 }
385 }
387 #=== FUNCTION ================================================================
388 # NAME: import_modules
389 # PARAMETERS: module_path - string - abs. path to the directory the modules
390 # are stored
391 # RETURNS: nothing
392 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
393 # state is on is imported by "require 'file';"
394 #===============================================================================
395 sub import_modules {
396 daemon_log(" ", 1);
398 if (not -e $modules_path) {
399 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
400 }
402 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
403 while (defined (my $file = readdir (DIR))) {
404 if (not $file =~ /(\S*?).pm$/) {
405 next;
406 }
407 my $mod_name = $1;
409 if( $file =~ /ArpHandler.pm/ ) {
410 if( $no_arp > 0 ) {
411 next;
412 }
413 }
415 eval { require $file; };
416 if ($@) {
417 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
418 daemon_log("$@", 5);
419 } else {
420 my $info = eval($mod_name.'::get_module_info()');
421 # Only load module if get_module_info() returns a non-null object
422 if( $info ) {
423 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
424 $known_modules->{$mod_name} = $info;
425 daemon_log("INFO: module $mod_name loaded", 5);
426 }
427 }
428 }
429 close (DIR);
430 }
433 #=== FUNCTION ================================================================
434 # NAME: sig_int_handler
435 # PARAMETERS: signal - string - signal arose from system
436 # RETURNS: noting
437 # DESCRIPTION: handels tasks to be done befor signal becomes active
438 #===============================================================================
439 sub sig_int_handler {
440 my ($signal) = @_;
442 # if (defined($ldap_handle)) {
443 # $ldap_handle->disconnect;
444 # }
445 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
448 daemon_log("shutting down gosa-si-server", 1);
449 system("kill `ps -C gosa-si-server -o pid=`");
450 }
451 $SIG{INT} = \&sig_int_handler;
454 sub check_key_and_xml_validity {
455 my ($crypted_msg, $module_key, $session_id) = @_;
456 my $msg;
457 my $msg_hash;
458 my $error_string;
459 eval{
460 $msg = &decrypt_msg($crypted_msg, $module_key);
462 if ($msg =~ /<xml>/i){
463 $msg =~ s/\s+/ /g; # just for better daemon_log
464 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
465 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
467 ##############
468 # check header
469 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
470 my $header_l = $msg_hash->{'header'};
471 if( 1 > @{$header_l} ) { die 'empty header tag'; }
472 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
473 my $header = @{$header_l}[0];
474 if( 0 == length $header) { die 'empty string in header tag'; }
476 ##############
477 # check source
478 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
479 my $source_l = $msg_hash->{'source'};
480 if( 1 > @{$source_l} ) { die 'empty source tag'; }
481 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
482 my $source = @{$source_l}[0];
483 if( 0 == length $source) { die 'source error'; }
485 ##############
486 # check target
487 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
488 my $target_l = $msg_hash->{'target'};
489 if( 1 > @{$target_l} ) { die 'empty target tag'; }
490 }
491 };
492 if($@) {
493 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
494 $msg = undef;
495 $msg_hash = undef;
496 }
498 return ($msg, $msg_hash);
499 }
502 sub check_outgoing_xml_validity {
503 my ($msg) = @_;
505 my $msg_hash;
506 eval{
507 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
509 ##############
510 # check header
511 my $header_l = $msg_hash->{'header'};
512 if( 1 != @{$header_l} ) {
513 die 'no or more than one headers specified';
514 }
515 my $header = @{$header_l}[0];
516 if( 0 == length $header) {
517 die 'header has length 0';
518 }
520 ##############
521 # check source
522 my $source_l = $msg_hash->{'source'};
523 if( 1 != @{$source_l} ) {
524 die 'no or more than 1 sources specified';
525 }
526 my $source = @{$source_l}[0];
527 if( 0 == length $source) {
528 die 'source has length 0';
529 }
530 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
531 $source =~ /^GOSA$/i ) {
532 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
533 }
535 ##############
536 # check target
537 my $target_l = $msg_hash->{'target'};
538 if( 0 == @{$target_l} ) {
539 die "no targets specified";
540 }
541 foreach my $target (@$target_l) {
542 if( 0 == length $target) {
543 die "target has length 0";
544 }
545 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
546 $target =~ /^GOSA$/i ||
547 $target =~ /^\*$/ ||
548 $target =~ /KNOWN_SERVER/i ||
549 $target =~ /JOBDB/i ||
550 $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 ){
551 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
552 }
553 }
554 };
555 if($@) {
556 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
557 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
558 $msg_hash = undef;
559 }
561 return ($msg_hash);
562 }
565 sub input_from_known_server {
566 my ($input, $remote_ip, $session_id) = @_ ;
567 my ($msg, $msg_hash, $module);
569 my $sql_statement= "SELECT * FROM known_server";
570 my $query_res = $known_server_db->select_dbentry( $sql_statement );
572 while( my ($hit_num, $hit) = each %{ $query_res } ) {
573 my $host_name = $hit->{hostname};
574 if( not $host_name =~ "^$remote_ip") {
575 next;
576 }
577 my $host_key = $hit->{hostkey};
578 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
579 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
581 # check if module can open msg envelope with module key
582 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
583 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
584 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
585 daemon_log("$@", 8);
586 next;
587 }
588 else {
589 $msg = $tmp_msg;
590 $msg_hash = $tmp_msg_hash;
591 $module = "SIPackages";
592 last;
593 }
594 }
596 if( (!$msg) || (!$msg_hash) || (!$module) ) {
597 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
598 }
600 return ($msg, $msg_hash, $module);
601 }
604 sub input_from_known_client {
605 my ($input, $remote_ip, $session_id) = @_ ;
606 my ($msg, $msg_hash, $module);
608 my $sql_statement= "SELECT * FROM known_clients";
609 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
610 while( my ($hit_num, $hit) = each %{ $query_res } ) {
611 my $host_name = $hit->{hostname};
612 if( not $host_name =~ /^$remote_ip:\d*$/) {
613 next;
614 }
615 my $host_key = $hit->{hostkey};
616 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
617 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
619 # check if module can open msg envelope with module key
620 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
622 if( (!$msg) || (!$msg_hash) ) {
623 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
624 &daemon_log("$@", 8);
625 next;
626 }
627 else {
628 $module = "SIPackages";
629 last;
630 }
631 }
633 if( (!$msg) || (!$msg_hash) || (!$module) ) {
634 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
635 }
637 return ($msg, $msg_hash, $module);
638 }
641 sub input_from_unknown_host {
642 no strict "refs";
643 my ($input, $session_id) = @_ ;
644 my ($msg, $msg_hash, $module);
645 my $error_string;
647 my %act_modules = %$known_modules;
649 while( my ($mod, $info) = each(%act_modules)) {
651 # check a key exists for this module
652 my $module_key = ${$mod."_key"};
653 if( not defined $module_key ) {
654 if( $mod eq 'ArpHandler' ) {
655 next;
656 }
657 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
658 next;
659 }
660 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
662 # check if module can open msg envelope with module key
663 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
664 if( (not defined $msg) || (not defined $msg_hash) ) {
665 next;
666 }
667 else {
668 $module = $mod;
669 last;
670 }
671 }
673 if( (!$msg) || (!$msg_hash) || (!$module)) {
674 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
675 }
677 return ($msg, $msg_hash, $module);
678 }
681 sub create_ciphering {
682 my ($passwd) = @_;
683 if((!defined($passwd)) || length($passwd)==0) {
684 $passwd = "";
685 }
686 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
687 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
688 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
689 $my_cipher->set_iv($iv);
690 return $my_cipher;
691 }
694 sub encrypt_msg {
695 my ($msg, $key) = @_;
696 my $my_cipher = &create_ciphering($key);
697 my $len;
698 {
699 use bytes;
700 $len= 16-length($msg)%16;
701 }
702 $msg = "\0"x($len).$msg;
703 $msg = $my_cipher->encrypt($msg);
704 chomp($msg = &encode_base64($msg));
705 # there are no newlines allowed inside msg
706 $msg=~ s/\n//g;
707 return $msg;
708 }
711 sub decrypt_msg {
713 my ($msg, $key) = @_ ;
714 $msg = &decode_base64($msg);
715 my $my_cipher = &create_ciphering($key);
716 $msg = $my_cipher->decrypt($msg);
717 $msg =~ s/\0*//g;
718 return $msg;
719 }
722 sub get_encrypt_key {
723 my ($target) = @_ ;
724 my $encrypt_key;
725 my $error = 0;
727 # target can be in known_server
728 if( not defined $encrypt_key ) {
729 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
730 my $query_res = $known_server_db->select_dbentry( $sql_statement );
731 while( my ($hit_num, $hit) = each %{ $query_res } ) {
732 my $host_name = $hit->{hostname};
733 if( $host_name ne $target ) {
734 next;
735 }
736 $encrypt_key = $hit->{hostkey};
737 last;
738 }
739 }
741 # target can be in known_client
742 if( not defined $encrypt_key ) {
743 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
744 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
745 while( my ($hit_num, $hit) = each %{ $query_res } ) {
746 my $host_name = $hit->{hostname};
747 if( $host_name ne $target ) {
748 next;
749 }
750 $encrypt_key = $hit->{hostkey};
751 last;
752 }
753 }
755 return $encrypt_key;
756 }
759 #=== FUNCTION ================================================================
760 # NAME: open_socket
761 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
762 # [PeerPort] string necessary if port not appended by PeerAddr
763 # RETURNS: socket IO::Socket::INET
764 # DESCRIPTION: open a socket to PeerAddr
765 #===============================================================================
766 sub open_socket {
767 my ($PeerAddr, $PeerPort) = @_ ;
768 if(defined($PeerPort)){
769 $PeerAddr = $PeerAddr.":".$PeerPort;
770 }
771 my $socket;
772 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
773 Porto => "tcp",
774 Type => SOCK_STREAM,
775 Timeout => 5,
776 );
777 if(not defined $socket) {
778 return;
779 }
780 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
781 return $socket;
782 }
785 #=== FUNCTION ================================================================
786 # NAME: get_ip
787 # PARAMETERS: interface name (i.e. eth0)
788 # RETURNS: (ip address)
789 # DESCRIPTION: Uses ioctl to get ip address directly from system.
790 #===============================================================================
791 sub get_ip {
792 my $ifreq= shift;
793 my $result= "";
794 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
795 my $proto= getprotobyname('ip');
797 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
798 or die "socket: $!";
800 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
801 my ($if, $sin) = unpack 'a16 a16', $ifreq;
802 my ($port, $addr) = sockaddr_in $sin;
803 my $ip = inet_ntoa $addr;
805 if ($ip && length($ip) > 0) {
806 $result = $ip;
807 }
808 }
810 return $result;
811 }
814 sub get_local_ip_for_remote_ip {
815 my $remote_ip= shift;
816 my $result="0.0.0.0";
818 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
819 if($remote_ip eq "127.0.0.1") {
820 $result = "127.0.0.1";
821 } else {
822 my $PROC_NET_ROUTE= ('/proc/net/route');
824 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
825 or die "Could not open $PROC_NET_ROUTE";
827 my @ifs = <PROC_NET_ROUTE>;
829 close(PROC_NET_ROUTE);
831 # Eat header line
832 shift @ifs;
833 chomp @ifs;
834 foreach my $line(@ifs) {
835 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
836 my $destination;
837 my $mask;
838 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
839 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
840 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
841 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
842 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
843 # destination matches route, save mac and exit
844 $result= &get_ip($Iface);
845 last;
846 }
847 }
848 }
849 } else {
850 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
851 }
852 return $result;
853 }
856 sub send_msg_to_target {
857 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
858 my $error = 0;
859 my $header;
860 my $new_status;
861 my $act_status;
862 my ($sql_statement, $res);
864 if( $msg_header ) {
865 $header = "'$msg_header'-";
866 } else {
867 $header = "";
868 }
870 # Patch the source ip
871 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
872 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
873 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
874 }
876 # encrypt xml msg
877 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
879 # opensocket
880 my $socket = &open_socket($address);
881 if( !$socket ) {
882 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
883 $error++;
884 }
886 if( $error == 0 ) {
887 # send xml msg
888 print $socket $crypted_msg."\n";
890 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
891 #daemon_log("DEBUG: message:\n$msg", 9);
893 }
895 # close socket in any case
896 if( $socket ) {
897 close $socket;
898 }
900 if( $error > 0 ) { $new_status = "down"; }
901 else { $new_status = $msg_header; }
904 # known_clients
905 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
906 $res = $known_clients_db->select_dbentry($sql_statement);
907 if( keys(%$res) > 0) {
908 $act_status = $res->{1}->{'status'};
909 if( $act_status eq "down" ) {
910 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
911 $res = $known_clients_db->del_dbentry($sql_statement);
912 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
913 } else {
914 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
915 $res = $known_clients_db->update_dbentry($sql_statement);
916 if($new_status eq "down"){
917 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
918 } else {
919 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
920 }
921 }
922 }
924 # known_server
925 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
926 $res = $known_server_db->select_dbentry($sql_statement);
927 if( keys(%$res) > 0 ) {
928 $act_status = $res->{1}->{'status'};
929 if( $act_status eq "down" ) {
930 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
931 $res = $known_server_db->del_dbentry($sql_statement);
932 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
933 }
934 else {
935 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
936 $res = $known_server_db->update_dbentry($sql_statement);
937 if($new_status eq "down"){
938 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
939 }
940 else {
941 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
942 }
943 }
944 }
945 return $error;
946 }
949 sub update_jobdb_status_for_send_msgs {
950 my ($answer, $error) = @_;
951 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
952 my $jobdb_id = $1;
954 # sending msg faild
955 if( $error ) {
956 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
957 my $sql_statement = "UPDATE $job_queue_tn ".
958 "SET status='error', result='can not deliver msg, please consult log file' ".
959 "WHERE id='$jobdb_id'";
960 my $res = $job_db->update_dbentry($sql_statement);
961 }
963 # sending msg was successful
964 } else {
965 my $sql_statement = "UPDATE $job_queue_tn ".
966 "SET status='done' ".
967 "WHERE id='$jobdb_id' AND status='processed'";
968 my $res = $job_db->update_dbentry($sql_statement);
969 }
970 }
971 }
973 sub _start {
974 my ($kernel) = $_[KERNEL];
975 &trigger_db_loop($kernel);
976 $global_kernel = $kernel;
977 $kernel->yield('create_fai_server_db', $fai_server_tn );
978 $kernel->yield('create_fai_release_db', $fai_release_tn );
979 $kernel->sig(USR1 => "sig_handler");
980 $kernel->sig(USR2 => "create_packages_list_db");
981 }
983 sub sig_handler {
984 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
985 daemon_log("0 INFO got signal '$signal'", 1);
986 $kernel->sig_handled();
987 return;
988 }
990 sub next_task {
991 my ($session, $heap) = @_[SESSION, HEAP];
993 while ( keys( %{ $heap->{task} } ) < $max_children ) {
994 my $next_task = shift @tasks;
995 last unless defined $next_task;
997 my $task = POE::Wheel::Run->new(
998 Program => sub { process_task($session, $heap, $next_task) },
999 StdioFilter => POE::Filter::Reference->new(),
1000 StdoutEvent => "task_result",
1001 StderrEvent => "task_debug",
1002 CloseEvent => "task_done",
1003 );
1005 $heap->{task}->{ $task->ID } = $task;
1006 }
1007 }
1009 sub handle_task_result {
1010 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1011 my $client_answer = $result->{'answer'};
1012 if( $client_answer =~ s/session_id=(\d+)$// ) {
1013 my $session_id = $1;
1014 if( defined $session_id ) {
1015 my $session_reference = $kernel->ID_id_to_session($session_id);
1016 if( defined $session_reference ) {
1017 $heap = $session_reference->get_heap();
1018 }
1019 }
1021 if(exists $heap->{'client'}) {
1022 $heap->{'client'}->put($client_answer);
1023 }
1024 }
1025 $kernel->sig(CHLD => "child_reap");
1026 }
1028 sub handle_task_debug {
1029 my $result = $_[ARG0];
1030 print STDERR "$result\n";
1031 }
1033 sub handle_task_done {
1034 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1035 delete $heap->{task}->{$task_id};
1036 $kernel->yield("next_task");
1037 }
1039 sub process_task {
1040 no strict "refs";
1041 my ($session, $heap, $input) = @_;
1042 my $session_id = $session->ID;
1043 my ($msg, $msg_hash, $module);
1044 my $error = 0;
1045 my $answer_l;
1046 my ($answer_header, @answer_target_l, $answer_source);
1047 my $client_answer = "";
1049 daemon_log("", 5);
1050 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1051 #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1053 ####################
1054 # check incoming msg
1055 # msg is from a new client or gosa
1056 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1057 # msg is from a gosa-si-server or gosa-si-bus
1058 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1059 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1060 }
1061 # msg is from a gosa-si-client
1062 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1063 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1064 }
1065 # an error occurred
1066 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1067 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1068 # could not understand a msg from its server the client cause a re-registering process
1069 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);
1070 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1071 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1072 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1073 my $host_name = $hit->{'hostname'};
1074 my $host_key = $hit->{'hostkey'};
1075 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1076 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1077 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1078 }
1079 $error++;
1080 }
1082 ######################
1083 # process incoming msg
1084 if( $error == 0) {
1085 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1086 "' from '".$heap->{'remote_ip'}."'", 5);
1087 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1088 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1090 if ( 0 < @{$answer_l} ) {
1091 my $answer_str = join("\n", @{$answer_l});
1092 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1093 } else {
1094 daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1095 }
1097 }
1098 if( !$answer_l ) { $error++ };
1100 ########
1101 # answer
1102 if( $error == 0 ) {
1104 foreach my $answer ( @{$answer_l} ) {
1105 # for each answer in answer list
1107 # check outgoing msg to xml validity
1108 my $answer_hash = &check_outgoing_xml_validity($answer);
1109 if( not defined $answer_hash ) {
1110 next;
1111 }
1113 $answer_header = @{$answer_hash->{'header'}}[0];
1114 @answer_target_l = @{$answer_hash->{'target'}};
1115 $answer_source = @{$answer_hash->{'source'}}[0];
1117 # deliver msg to all targets
1118 foreach my $answer_target ( @answer_target_l ) {
1120 # targets of msg are all gosa-si-clients in known_clients_db
1121 if( $answer_target eq "*" ) {
1122 # answer is for all clients
1123 my $sql_statement= "SELECT * FROM known_clients";
1124 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1125 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1126 my $host_name = $hit->{hostname};
1127 my $host_key = $hit->{hostkey};
1128 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1129 &update_jobdb_status_for_send_msgs($answer, $error);
1130 }
1131 }
1133 # targets of msg are all gosa-si-server in known_server_db
1134 elsif( $answer_target eq "KNOWN_SERVER" ) {
1135 # answer is for all server in known_server
1136 my $sql_statement= "SELECT * FROM known_server";
1137 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1138 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1139 my $host_name = $hit->{hostname};
1140 my $host_key = $hit->{hostkey};
1141 $answer =~ s/KNOWN_SERVER/$host_name/g;
1142 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1143 &update_jobdb_status_for_send_msgs($answer, $error);
1144 }
1145 }
1147 # target of msg is GOsa
1148 elsif( $answer_target eq "GOSA" ) {
1149 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1150 my $add_on = "";
1151 if( defined $session_id ) {
1152 $add_on = ".session_id=$session_id";
1153 }
1154 # answer is for GOSA and has to returned to connected client
1155 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1156 $client_answer = $gosa_answer.$add_on;
1157 }
1159 # target of msg is job queue at this host
1160 elsif( $answer_target eq "JOBDB") {
1161 $answer =~ /<header>(\S+)<\/header>/;
1162 my $header;
1163 if( defined $1 ) { $header = $1; }
1164 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1165 &update_jobdb_status_for_send_msgs($answer, $error);
1166 }
1168 # target of msg is a mac address
1169 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 ) {
1170 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1171 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1172 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1173 my $found_ip_flag = 0;
1174 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1175 my $host_name = $hit->{hostname};
1176 my $host_key = $hit->{hostkey};
1177 $answer =~ s/$answer_target/$host_name/g;
1178 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1179 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1180 &update_jobdb_status_for_send_msgs($answer, $error);
1181 $found_ip_flag++ ;
1182 }
1183 if( $found_ip_flag == 0) {
1184 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1185 if( $bus_activ eq "true" ) {
1186 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1187 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1188 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1189 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1190 my $bus_address = $hit->{hostname};
1191 my $bus_key = $hit->{hostkey};
1192 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1193 &update_jobdb_status_for_send_msgs($answer, $error);
1194 last;
1195 }
1196 }
1198 }
1200 # answer is for one specific host
1201 } else {
1202 # get encrypt_key
1203 my $encrypt_key = &get_encrypt_key($answer_target);
1204 if( not defined $encrypt_key ) {
1205 # unknown target, forward msg to bus
1206 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1207 if( $bus_activ eq "true" ) {
1208 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1209 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1210 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1211 my $res_length = keys( %{$query_res} );
1212 if( $res_length == 0 ){
1213 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1214 "no bus found in known_server", 3);
1215 }
1216 else {
1217 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1218 my $bus_key = $hit->{hostkey};
1219 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1220 &update_jobdb_status_for_send_msgs($answer, $error);
1221 }
1222 }
1223 }
1224 next;
1225 }
1226 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1227 &update_jobdb_status_for_send_msgs($answer, $error);
1228 }
1229 }
1230 }
1231 }
1233 my $filter = POE::Filter::Reference->new();
1234 my %result = (
1235 status => "seems ok to me",
1236 answer => $client_answer,
1237 );
1239 my $output = $filter->put( [ \%result ] );
1240 print @$output;
1243 }
1246 sub trigger_db_loop {
1247 my ($kernel) = @_ ;
1248 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1249 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1250 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1251 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1252 }
1254 sub watch_for_done_jobs {
1255 my ($kernel,$heap) = @_[KERNEL, HEAP];
1257 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1258 " WHERE status='done'";
1259 my $res = $job_db->select_dbentry( $sql_statement );
1261 while( my ($id, $hit) = each %{$res} ) {
1262 my $jobdb_id = $hit->{id};
1263 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1264 my $res = $job_db->del_dbentry($sql_statement);
1265 }
1267 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1268 }
1270 sub watch_for_new_jobs {
1271 my ($kernel,$heap) = @_[KERNEL, HEAP];
1273 # check gosa job queue for jobs with executable timestamp
1274 my $timestamp = &get_time();
1275 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1276 my $res = $job_db->exec_statement( $sql_statement );
1278 # Merge all new jobs that would do the same actions
1279 my @drops;
1280 my $hits;
1281 foreach my $hit (reverse @{$res} ) {
1282 my $macaddress= lc @{$hit}[8];
1283 my $headertag= @{$hit}[5];
1284 if(
1285 defined($hits->{$macaddress}) &&
1286 defined($hits->{$macaddress}->{$headertag}) &&
1287 defined($hits->{$macaddress}->{$headertag}[0])
1288 ) {
1289 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1290 }
1291 $hits->{$macaddress}->{$headertag}= $hit;
1292 }
1294 # Delete new jobs with a matching job in state 'processing'
1295 foreach my $macaddress (keys %{$hits}) {
1296 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1297 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1298 if(defined($job_db_id)) {
1299 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1300 my $res = $job_db->exec_statement( $sql_statement );
1301 foreach my $hit (@{$res}) {
1302 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1303 }
1304 } else {
1305 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1306 }
1307 }
1308 }
1310 # Commit deletion
1311 $job_db->exec_statementlist(\@drops);
1313 # Look for new jobs that could be executed
1314 foreach my $macaddress (keys %{$hits}) {
1316 # Look if there is an executing job
1317 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1318 my $res = $job_db->exec_statement( $sql_statement );
1320 # Skip new jobs for host if there is a processing job
1321 if(defined($res) and defined @{$res}[0]) {
1322 next;
1323 }
1325 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1326 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1327 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1329 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1330 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1331 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1333 # expect macaddress is unique!!!!!!
1334 my $target = $res_hash->{1}->{hostname};
1336 # change header
1337 $job_msg =~ s/<header>job_/<header>gosa_/;
1339 # add sqlite_id
1340 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1342 $job_msg =~ /<header>(\S+)<\/header>/;
1343 my $header = $1 ;
1344 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1346 # update status in job queue to 'processing'
1347 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1348 my $res = $job_db->update_dbentry($sql_statement);
1350 # We don't want parallel processing
1351 last;
1352 }
1353 }
1355 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1356 }
1359 sub watch_for_new_messages {
1360 my ($kernel,$heap) = @_[KERNEL, HEAP];
1361 my @coll_user_msg; # collection list of outgoing messages
1363 # check messaging_db for new incoming messages with executable timestamp
1364 my $timestamp = &get_time();
1365 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1366 my $res = $messaging_db->exec_statement( $sql_statement );
1367 foreach my $hit (@{$res}) {
1369 # create outgoing messages
1370 my $message_to = @{$hit}[3];
1372 # translate message_to to plain login name
1373 # TODO implement reciever translation
1374 my @reciever_l = ($message_to);
1375 my $message_id = @{$hit}[0];
1377 #add each outgoing msg to messaging_db
1378 my $reciever;
1379 foreach $reciever (@reciever_l) {
1380 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1381 "VALUES ('".
1382 $message_id."', '". # id
1383 @{$hit}[1]."', '". # subject
1384 @{$hit}[2]."', '". # message_from
1385 $reciever."', '". # message_to
1386 "none"."', '". # flag
1387 "out"."', '". # direction
1388 @{$hit}[6]."', '". # delivery_time
1389 @{$hit}[7]."', '". # message
1390 $timestamp."'". # timestamp
1391 ")";
1392 &daemon_log("M DEBUG: $sql_statement", 1);
1393 my $res = $messaging_db->exec_statement($sql_statement);
1394 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to reciever '$reciever'", 5);
1395 }
1397 # send outgoing messages
1398 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1399 my $res = $messaging_db->exec_statement( $sql_statement );
1400 foreach my $hit (@{$res}) {
1401 # add subject, from, to and message to list coll_user_msg
1402 my @user_msg = [@{$hit}[1], @{$hit}[2], $reciever, @{$hit}[7]];
1403 push( @coll_user_msg, \@user_msg);
1404 }
1406 # send outgoing list to myself (gosa-si-server) to deliver each message to user
1407 # reason for this workaround: if to much messages have to be delivered, it can come to
1408 # denial of service problems of the server. so, the incoming message list can be processed
1409 # by a forked child and gosa-si-server is always ready to work.
1410 my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1411 # add to hash 'msg1' => [subject, from, to, message]
1412 # hash to string
1413 # send msg to myself
1414 # TODO
1416 # set incoming message to flag d=deliverd
1417 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1418 &daemon_log("M DEBUG: $sql_statement", 7);
1419 $res = $messaging_db->update_dbentry($sql_statement);
1420 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1422 }
1424 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1427 return;
1428 }
1431 sub watch_for_done_messages {
1432 my ($kernel,$heap) = @_[KERNEL, HEAP];
1434 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1435 return;
1436 }
1439 sub get_ldap_handle {
1440 my ($session_id) = @_;
1441 my $heap;
1442 my $ldap_handle;
1444 if (not defined $session_id ) { $session_id = 0 };
1445 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1447 if ($session_id == 0) {
1448 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1449 $ldap_handle = Net::LDAP->new( $ldap_uri );
1450 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1452 } else {
1453 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1454 if( defined $session_reference ) {
1455 $heap = $session_reference->get_heap();
1456 }
1458 if (not defined $heap) {
1459 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1460 return;
1461 }
1463 # TODO: This "if" is nonsense, because it doesn't prove that the
1464 # used handle is still valid - or if we've to reconnect...
1465 #if (not exists $heap->{ldap_handle}) {
1466 $ldap_handle = Net::LDAP->new( $ldap_uri );
1467 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1468 $heap->{ldap_handle} = $ldap_handle;
1469 #}
1470 }
1471 return $ldap_handle;
1472 }
1475 sub change_fai_state {
1476 my ($st, $targets, $session_id) = @_;
1477 $session_id = 0 if not defined $session_id;
1478 # Set FAI state to localboot
1479 my %mapActions= (
1480 reboot => '',
1481 update => 'softupdate',
1482 localboot => 'localboot',
1483 reinstall => 'install',
1484 rescan => '',
1485 wake => '',
1486 memcheck => 'memcheck',
1487 sysinfo => 'sysinfo',
1488 install => 'install',
1489 );
1491 # Return if this is unknown
1492 if (!exists $mapActions{ $st }){
1493 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1494 return;
1495 }
1497 my $state= $mapActions{ $st };
1499 my $ldap_handle = &get_ldap_handle($session_id);
1500 if( defined($ldap_handle) ) {
1502 # Build search filter for hosts
1503 my $search= "(&(objectClass=GOhard)";
1504 foreach (@{$targets}){
1505 $search.= "(macAddress=$_)";
1506 }
1507 $search.= ")";
1509 # If there's any host inside of the search string, procress them
1510 if (!($search =~ /macAddress/)){
1511 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1512 return;
1513 }
1515 # Perform search for Unit Tag
1516 my $mesg = $ldap_handle->search(
1517 base => $ldap_base,
1518 scope => 'sub',
1519 attrs => ['dn', 'FAIstate', 'objectClass'],
1520 filter => "$search"
1521 );
1523 if ($mesg->count) {
1524 my @entries = $mesg->entries;
1525 foreach my $entry (@entries) {
1526 # Only modify entry if it is not set to '$state'
1527 if ($entry->get_value("FAIstate") ne "$state"){
1528 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1529 my $result;
1530 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1531 if (exists $tmp{'FAIobject'}){
1532 if ($state eq ''){
1533 $result= $ldap_handle->modify($entry->dn, changes => [
1534 delete => [ FAIstate => [] ] ]);
1535 } else {
1536 $result= $ldap_handle->modify($entry->dn, changes => [
1537 replace => [ FAIstate => $state ] ]);
1538 }
1539 } elsif ($state ne ''){
1540 $result= $ldap_handle->modify($entry->dn, changes => [
1541 add => [ objectClass => 'FAIobject' ],
1542 add => [ FAIstate => $state ] ]);
1543 }
1545 # Errors?
1546 if ($result->code){
1547 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1548 }
1549 } else {
1550 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1551 }
1552 }
1553 }
1554 # if no ldap handle defined
1555 } else {
1556 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1557 }
1559 }
1562 sub change_goto_state {
1563 my ($st, $targets, $session_id) = @_;
1564 $session_id = 0 if not defined $session_id;
1566 # Switch on or off?
1567 my $state= $st eq 'active' ? 'active': 'locked';
1569 my $ldap_handle = &get_ldap_handle($session_id);
1570 if( defined($ldap_handle) ) {
1572 # Build search filter for hosts
1573 my $search= "(&(objectClass=GOhard)";
1574 foreach (@{$targets}){
1575 $search.= "(macAddress=$_)";
1576 }
1577 $search.= ")";
1579 # If there's any host inside of the search string, procress them
1580 if (!($search =~ /macAddress/)){
1581 return;
1582 }
1584 # Perform search for Unit Tag
1585 my $mesg = $ldap_handle->search(
1586 base => $ldap_base,
1587 scope => 'sub',
1588 attrs => ['dn', 'gotoMode'],
1589 filter => "$search"
1590 );
1592 if ($mesg->count) {
1593 my @entries = $mesg->entries;
1594 foreach my $entry (@entries) {
1596 # Only modify entry if it is not set to '$state'
1597 if ($entry->get_value("gotoMode") ne $state){
1599 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1600 my $result;
1601 $result= $ldap_handle->modify($entry->dn, changes => [
1602 replace => [ gotoMode => $state ] ]);
1604 # Errors?
1605 if ($result->code){
1606 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1607 }
1609 }
1610 }
1611 }
1613 }
1614 }
1617 sub run_create_fai_server_db {
1618 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1619 my $session_id = $session->ID;
1620 my $task = POE::Wheel::Run->new(
1621 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1622 StdoutEvent => "session_run_result",
1623 StderrEvent => "session_run_debug",
1624 CloseEvent => "session_run_done",
1625 );
1627 $heap->{task}->{ $task->ID } = $task;
1628 return;
1629 }
1632 sub create_fai_server_db {
1633 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1634 my $result;
1636 if (not defined $session_id) { $session_id = 0; }
1637 my $ldap_handle = &get_ldap_handle();
1638 if(defined($ldap_handle)) {
1639 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1640 my $mesg= $ldap_handle->search(
1641 base => $ldap_base,
1642 scope => 'sub',
1643 attrs => ['FAIrepository', 'gosaUnitTag'],
1644 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1645 );
1646 if($mesg->{'resultCode'} == 0 &&
1647 $mesg->count != 0) {
1648 foreach my $entry (@{$mesg->{entries}}) {
1649 if($entry->exists('FAIrepository')) {
1650 # Add an entry for each Repository configured for server
1651 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1652 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1653 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1654 $result= $fai_server_db->add_dbentry( {
1655 table => $table_name,
1656 primkey => ['server', 'release', 'tag'],
1657 server => $tmp_url,
1658 release => $tmp_release,
1659 sections => $tmp_sections,
1660 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1661 } );
1662 }
1663 }
1664 }
1665 }
1666 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1668 # TODO: Find a way to post the 'create_packages_list_db' event
1669 if(not defined($dont_create_packages_list)) {
1670 &create_packages_list_db(undef, undef, $session_id);
1671 }
1672 }
1674 $ldap_handle->disconnect;
1675 return $result;
1676 }
1679 sub run_create_fai_release_db {
1680 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1681 my $session_id = $session->ID;
1682 my $task = POE::Wheel::Run->new(
1683 Program => sub { &create_fai_release_db($table_name, $session_id) },
1684 StdoutEvent => "session_run_result",
1685 StderrEvent => "session_run_debug",
1686 CloseEvent => "session_run_done",
1687 );
1689 $heap->{task}->{ $task->ID } = $task;
1690 return;
1691 }
1694 sub create_fai_release_db {
1695 my ($table_name, $session_id) = @_;
1696 my $result;
1698 # used for logging
1699 if (not defined $session_id) { $session_id = 0; }
1701 my $ldap_handle = &get_ldap_handle();
1702 if(defined($ldap_handle)) {
1703 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1704 my $mesg= $ldap_handle->search(
1705 base => $ldap_base,
1706 scope => 'sub',
1707 attrs => [],
1708 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1709 );
1710 if($mesg->{'resultCode'} == 0 &&
1711 $mesg->count != 0) {
1712 # Walk through all possible FAI container ou's
1713 my @sql_list;
1714 my $timestamp= &get_time();
1715 foreach my $ou (@{$mesg->{entries}}) {
1716 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1717 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1718 my @tmp_array=get_fai_release_entries($tmp_classes);
1719 if(@tmp_array) {
1720 foreach my $entry (@tmp_array) {
1721 if(defined($entry) && ref($entry) eq 'HASH') {
1722 my $sql=
1723 "INSERT INTO $table_name "
1724 ."(timestamp, release, class, type, state) VALUES ("
1725 .$timestamp.","
1726 ."'".$entry->{'release'}."',"
1727 ."'".$entry->{'class'}."',"
1728 ."'".$entry->{'type'}."',"
1729 ."'".$entry->{'state'}."')";
1730 push @sql_list, $sql;
1731 }
1732 }
1733 }
1734 }
1735 }
1737 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1738 if(@sql_list) {
1739 unshift @sql_list, "DELETE FROM $table_name"; # at first, clear db
1740 $fai_release_db->exec_statementlist(\@sql_list);
1741 }
1742 daemon_log("$session_id DEBUG: Done with inserting",7);
1743 }
1744 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1745 }
1746 $ldap_handle->disconnect;
1747 return $result;
1748 }
1750 sub get_fai_types {
1751 my $tmp_classes = shift || return undef;
1752 my @result;
1754 foreach my $type(keys %{$tmp_classes}) {
1755 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1756 my $entry = {
1757 type => $type,
1758 state => $tmp_classes->{$type}[0],
1759 };
1760 push @result, $entry;
1761 }
1762 }
1764 return @result;
1765 }
1767 sub get_fai_state {
1768 my $result = "";
1769 my $tmp_classes = shift || return $result;
1771 foreach my $type(keys %{$tmp_classes}) {
1772 if(defined($tmp_classes->{$type}[0])) {
1773 $result = $tmp_classes->{$type}[0];
1775 # State is equal for all types in class
1776 last;
1777 }
1778 }
1780 return $result;
1781 }
1783 sub resolve_fai_classes {
1784 my ($fai_base, $ldap_handle, $session_id) = @_;
1785 if (not defined $session_id) { $session_id = 0; }
1786 my $result;
1787 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1788 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1789 my $fai_classes;
1791 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1792 my $mesg= $ldap_handle->search(
1793 base => $fai_base,
1794 scope => 'sub',
1795 attrs => ['cn','objectClass','FAIstate'],
1796 filter => $fai_filter,
1797 );
1798 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1800 if($mesg->{'resultCode'} == 0 &&
1801 $mesg->count != 0) {
1802 foreach my $entry (@{$mesg->{entries}}) {
1803 if($entry->exists('cn')) {
1804 my $tmp_dn= $entry->dn();
1806 # Skip classname and ou dn parts for class
1807 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1809 # Skip classes without releases
1810 if((!defined($tmp_release)) || length($tmp_release)==0) {
1811 next;
1812 }
1814 my $tmp_cn= $entry->get_value('cn');
1815 my $tmp_state= $entry->get_value('FAIstate');
1817 my $tmp_type;
1818 # Get FAI type
1819 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1820 if(grep $_ eq $oclass, @possible_fai_classes) {
1821 $tmp_type= $oclass;
1822 last;
1823 }
1824 }
1826 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1827 # A Subrelease
1828 my @sub_releases = split(/,/, $tmp_release);
1830 # Walk through subreleases and build hash tree
1831 my $hash;
1832 while(my $tmp_sub_release = pop @sub_releases) {
1833 $hash .= "\{'$tmp_sub_release'\}->";
1834 }
1835 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1836 } else {
1837 # A branch, no subrelease
1838 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1839 }
1840 } elsif (!$entry->exists('cn')) {
1841 my $tmp_dn= $entry->dn();
1842 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1844 # Skip classes without releases
1845 if((!defined($tmp_release)) || length($tmp_release)==0) {
1846 next;
1847 }
1849 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1850 # A Subrelease
1851 my @sub_releases= split(/,/, $tmp_release);
1853 # Walk through subreleases and build hash tree
1854 my $hash;
1855 while(my $tmp_sub_release = pop @sub_releases) {
1856 $hash .= "\{'$tmp_sub_release'\}->";
1857 }
1858 # Remove the last two characters
1859 chop($hash);
1860 chop($hash);
1862 eval('$fai_classes->'.$hash.'= {}');
1863 } else {
1864 # A branch, no subrelease
1865 if(!exists($fai_classes->{$tmp_release})) {
1866 $fai_classes->{$tmp_release} = {};
1867 }
1868 }
1869 }
1870 }
1872 # The hash is complete, now we can honor the copy-on-write based missing entries
1873 foreach my $release (keys %$fai_classes) {
1874 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1875 }
1876 }
1877 return $result;
1878 }
1880 sub apply_fai_inheritance {
1881 my $fai_classes = shift || return {};
1882 my $tmp_classes;
1884 # Get the classes from the branch
1885 foreach my $class (keys %{$fai_classes}) {
1886 # Skip subreleases
1887 if($class =~ /^ou=.*$/) {
1888 next;
1889 } else {
1890 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1891 }
1892 }
1894 # Apply to each subrelease
1895 foreach my $subrelease (keys %{$fai_classes}) {
1896 if($subrelease =~ /ou=/) {
1897 foreach my $tmp_class (keys %{$tmp_classes}) {
1898 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1899 $fai_classes->{$subrelease}->{$tmp_class} =
1900 deep_copy($tmp_classes->{$tmp_class});
1901 } else {
1902 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1903 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1904 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1905 deep_copy($tmp_classes->{$tmp_class}->{$type});
1906 }
1907 }
1908 }
1909 }
1910 }
1911 }
1913 # Find subreleases in deeper levels
1914 foreach my $subrelease (keys %{$fai_classes}) {
1915 if($subrelease =~ /ou=/) {
1916 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1917 if($subsubrelease =~ /ou=/) {
1918 apply_fai_inheritance($fai_classes->{$subrelease});
1919 }
1920 }
1921 }
1922 }
1924 return $fai_classes;
1925 }
1927 sub get_fai_release_entries {
1928 my $tmp_classes = shift || return;
1929 my $parent = shift || "";
1930 my @result = shift || ();
1932 foreach my $entry (keys %{$tmp_classes}) {
1933 if(defined($entry)) {
1934 if($entry =~ /^ou=.*$/) {
1935 my $release_name = $entry;
1936 $release_name =~ s/ou=//g;
1937 if(length($parent)>0) {
1938 $release_name = $parent."/".$release_name;
1939 }
1940 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1941 foreach my $bufentry(@bufentries) {
1942 push @result, $bufentry;
1943 }
1944 } else {
1945 my @types = get_fai_types($tmp_classes->{$entry});
1946 foreach my $type (@types) {
1947 push @result,
1948 {
1949 'class' => $entry,
1950 'type' => $type->{'type'},
1951 'release' => $parent,
1952 'state' => $type->{'state'},
1953 };
1954 }
1955 }
1956 }
1957 }
1959 return @result;
1960 }
1962 sub deep_copy {
1963 my $this = shift;
1964 if (not ref $this) {
1965 $this;
1966 } elsif (ref $this eq "ARRAY") {
1967 [map deep_copy($_), @$this];
1968 } elsif (ref $this eq "HASH") {
1969 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1970 } else { die "what type is $_?" }
1971 }
1974 sub session_run_result {
1975 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1976 $kernel->sig(CHLD => "child_reap");
1977 }
1979 sub session_run_debug {
1980 my $result = $_[ARG0];
1981 print STDERR "$result\n";
1982 }
1984 sub session_run_done {
1985 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1986 delete $heap->{task}->{$task_id};
1987 }
1989 sub create_sources_list {
1990 my ($session_id) = @_;
1991 my $ldap_handle = &get_ldap_handle;
1992 my $result="/tmp/gosa_si_tmp_sources_list";
1994 # Remove old file
1995 if(stat($result)) {
1996 unlink($result);
1997 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
1998 }
2000 my $fh;
2001 open($fh, ">$result");
2002 if (not defined $fh) {
2003 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2004 return undef;
2005 }
2006 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
2007 my $mesg=$ldap_handle->search(
2008 base => $ldap_server_dn,
2009 scope => 'base',
2010 attrs => 'FAIrepository',
2011 filter => 'objectClass=FAIrepositoryServer'
2012 );
2013 if($mesg->count) {
2014 foreach my $entry(@{$mesg->{'entries'}}) {
2015 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2016 my ($server, $tag, $release, $sections)= split /\|/, $value;
2017 my $line = "deb $server $release";
2018 $sections =~ s/,/ /g;
2019 $line.= " $sections";
2020 print $fh $line."\n";
2021 }
2022 }
2023 }
2024 } else {
2025 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$ldap_server_dn', abort create_sources_list", 1);
2026 }
2027 close($fh);
2029 return $result;
2030 }
2033 sub run_create_packages_list_db {
2034 my ($session, $heap) = @_[SESSION, HEAP];
2035 my $session_id = $session->ID;
2037 my $task = POE::Wheel::Run->new(
2038 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2039 StdoutEvent => "session_run_result",
2040 StderrEvent => "session_run_debug",
2041 CloseEvent => "session_run_done",
2042 );
2043 $heap->{task}->{ $task->ID } = $task;
2044 }
2047 sub create_packages_list_db {
2048 my ($ldap_handle, $sources_file, $session_id) = @_;
2050 if (not defined $session_id) { $session_id = 0; }
2051 if (not defined $ldap_handle) {
2052 $ldap_handle= &get_ldap_handle();
2054 if (not defined $ldap_handle) {
2055 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2056 return;
2057 }
2058 }
2059 if (not defined $sources_file) {
2060 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2061 $sources_file = &create_sources_list($session_id);
2062 }
2064 if (not defined $sources_file) {
2065 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2067 }
2069 # it should not be possible to trigger a recreation of packages_list_db
2070 # while packages_list_db is under construction, so set flag packages_list_under_construction
2071 # which is tested befor recreation can be started
2072 if ($packages_list_under_construction) {
2073 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait untill this process is finished", 3);
2074 return;
2075 } else {
2076 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2077 # set packages_list_under_construction to true
2078 $packages_list_under_construction = 1;
2079 }
2080 my $line;
2082 open(CONFIG, "<$sources_file") or do {
2083 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2084 return;
2085 };
2087 # Read lines
2088 while ($line = <CONFIG>){
2089 # Unify
2090 chop($line);
2091 $line =~ s/^\s+//;
2092 $line =~ s/^\s+/ /;
2094 # Strip comments
2095 $line =~ s/#.*$//g;
2097 # Skip empty lines
2098 if ($line =~ /^\s*$/){
2099 next;
2100 }
2102 # Interpret deb line
2103 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2104 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2105 my $section;
2106 foreach $section (split(' ', $sections)){
2107 &parse_package_info( $baseurl, $dist, $section, $session_id );
2108 }
2109 }
2110 }
2112 close (CONFIG);
2114 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2115 # set packages_list_under_construction to false
2116 $packages_list_under_construction = 0;
2118 return;
2119 }
2122 sub parse_package_info {
2123 my ($baseurl, $dist, $section, $session_id)= @_;
2124 my ($package);
2125 if (not defined $session_id) { $session_id = 0; }
2126 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2127 $repo_dirs{ "${repo_path}/pool" } = 1;
2129 foreach $package ("Packages.gz"){
2130 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2131 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2132 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2133 }
2135 find(\&cleanup_and_extract, keys( %repo_dirs ));
2136 }
2139 sub get_package {
2140 my ($url, $dest, $session_id)= @_;
2141 if (not defined $session_id) { $session_id = 0; }
2143 my $tpath = dirname($dest);
2144 -d "$tpath" || mkpath "$tpath";
2146 # This is ugly, but I've no time to take a look at "how it works in perl"
2147 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2148 system("gunzip -cd '$dest' > '$dest.in'");
2149 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2150 unlink($dest);
2151 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2152 } else {
2153 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2154 }
2155 return 0;
2156 }
2159 sub parse_package {
2160 my ($path, $dist, $srv_path, $session_id)= @_;
2161 if (not defined $session_id) { $session_id = 0;}
2162 my ($package, $version, $section, $description);
2163 my @sql_list;
2164 my $PACKAGES;
2165 my $timestamp = &get_time();
2167 if(not stat("$path.in")) {
2168 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2169 return;
2170 }
2172 open($PACKAGES, "<$path.in");
2173 if(not defined($PACKAGES)) {
2174 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2175 return;
2176 }
2178 # Read lines
2179 while (<$PACKAGES>){
2180 my $line = $_;
2181 # Unify
2182 chop($line);
2184 # Use empty lines as a trigger
2185 if ($line =~ /^\s*$/){
2186 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '$timestamp')";
2187 push(@sql_list, $sql);
2188 $package = "none";
2189 $version = "none";
2190 $section = "none";
2191 $description = "none";
2192 next;
2193 }
2195 # Trigger for package name
2196 if ($line =~ /^Package:\s/){
2197 ($package)= ($line =~ /^Package: (.*)$/);
2198 next;
2199 }
2201 # Trigger for version
2202 if ($line =~ /^Version:\s/){
2203 ($version)= ($line =~ /^Version: (.*)$/);
2204 next;
2205 }
2207 # Trigger for description
2208 if ($line =~ /^Description:\s/){
2209 ($description)= ($line =~ /^Description: (.*)$/);
2210 next;
2211 }
2213 # Trigger for section
2214 if ($line =~ /^Section:\s/){
2215 ($section)= ($line =~ /^Section: (.*)$/);
2216 next;
2217 }
2219 # Trigger for filename
2220 if ($line =~ /^Filename:\s/){
2221 my ($filename) = ($line =~ /^Filename: (.*)$/);
2222 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2223 next;
2224 }
2225 }
2227 close( $PACKAGES );
2228 unlink( "$path.in" );
2229 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2230 my $len_sql_list = @sql_list;
2231 &main::daemon_log("$session_id DEBUG: add $len_sql_list insert-statements to packages_list_db", 5);
2232 $packages_list_db->exec_statementlist(\@sql_list);
2233 }
2236 sub store_fileinfo {
2237 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2239 my %fileinfo = (
2240 'package' => $package,
2241 'dist' => $dist,
2242 'version' => $vers,
2243 );
2245 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2246 }
2249 sub cleanup_and_extract {
2250 my $fileinfo = $repo_files{ $File::Find::name };
2252 if( defined $fileinfo ) {
2254 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2255 my $sql;
2256 my $package = $fileinfo->{ 'package' };
2257 my $newver = $fileinfo->{ 'version' };
2259 mkpath($dir);
2260 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2262 if( -f "$dir/DEBIAN/templates" ) {
2264 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2266 my $tmpl= "";
2267 {
2268 local $/=undef;
2269 open FILE, "$dir/DEBIAN/templates";
2270 $tmpl = &encode_base64(<FILE>);
2271 close FILE;
2272 }
2273 rmtree("$dir/DEBIAN/templates");
2275 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2277 } else {
2278 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2279 }
2281 my $res= $main::packages_list_db->update_dbentry($sql);
2282 }
2284 return;
2285 }
2288 #==== MAIN = main ==============================================================
2289 # parse commandline options
2290 Getopt::Long::Configure( "bundling" );
2291 GetOptions("h|help" => \&usage,
2292 "c|config=s" => \$cfg_file,
2293 "f|foreground" => \$foreground,
2294 "v|verbose+" => \$verbose,
2295 "no-bus+" => \$no_bus,
2296 "no-arp+" => \$no_arp,
2297 );
2299 # read and set config parameters
2300 &check_cmdline_param ;
2301 &read_configfile;
2302 &check_pid;
2304 $SIG{CHLD} = 'IGNORE';
2306 # forward error messages to logfile
2307 if( ! $foreground ) {
2308 open( STDIN, '+>/dev/null' );
2309 open( STDOUT, '+>&STDIN' );
2310 open( STDERR, '+>&STDIN' );
2311 }
2313 # Just fork, if we are not in foreground mode
2314 if( ! $foreground ) {
2315 chdir '/' or die "Can't chdir to /: $!";
2316 $pid = fork;
2317 setsid or die "Can't start a new session: $!";
2318 umask 0;
2319 } else {
2320 $pid = $$;
2321 }
2323 # Do something useful - put our PID into the pid_file
2324 if( 0 != $pid ) {
2325 open( LOCK_FILE, ">$pid_file" );
2326 print LOCK_FILE "$pid\n";
2327 close( LOCK_FILE );
2328 if( !$foreground ) {
2329 exit( 0 )
2330 };
2331 }
2333 daemon_log(" ", 1);
2334 daemon_log("$0 started!", 1);
2336 if ($no_bus > 0) {
2337 $bus_activ = "false"
2338 }
2340 # connect to gosa-si job queue
2341 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2342 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2344 # connect to known_clients_db
2345 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2346 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2348 # connect to known_server_db
2349 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2350 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2352 # connect to login_usr_db
2353 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2354 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2356 # connect to fai_server_db and fai_release_db
2357 unlink($fai_server_file_name);
2358 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2359 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2361 unlink($fai_release_file_name);
2362 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2363 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2365 # connect to packages_list_db
2366 unlink($packages_list_file_name);
2367 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2368 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2370 # connect to messaging_db
2371 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2372 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2375 # create xml object used for en/decrypting
2376 $xml = new XML::Simple();
2378 # create socket for incoming xml messages
2380 POE::Component::Server::TCP->new(
2381 Port => $server_port,
2382 ClientInput => sub {
2383 my ($kernel, $input) = @_[KERNEL, ARG0];
2384 push(@tasks, $input);
2385 $kernel->yield("next_task");
2386 },
2387 InlineStates => {
2388 next_task => \&next_task,
2389 task_result => \&handle_task_result,
2390 task_done => \&handle_task_done,
2391 task_debug => \&handle_task_debug,
2392 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2393 }
2394 );
2396 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2398 # create session for repeatedly checking the job queue for jobs
2399 POE::Session->create(
2400 inline_states => {
2401 _start => \&_start,
2402 sig_handler => \&sig_handler,
2403 watch_for_new_messages => \&watch_for_new_messages,
2404 watch_for_done_messages => \&watch_for_done_messages,
2405 watch_for_new_jobs => \&watch_for_new_jobs,
2406 watch_for_done_jobs => \&watch_for_done_jobs,
2407 create_packages_list_db => \&run_create_packages_list_db,
2408 create_fai_server_db => \&run_create_fai_server_db,
2409 create_fai_release_db => \&run_create_fai_release_db,
2410 session_run_result => \&session_run_result,
2411 session_run_debug => \&session_run_debug,
2412 session_run_done => \&session_run_done,
2413 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2414 }
2415 );
2418 # import all modules
2419 &import_modules;
2421 # check wether all modules are gosa-si valid passwd check
2423 POE::Kernel->run();
2424 exit;