cbf152842b36b94f2682386e6bef3f50c83b9602
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file,
86 $server_ip, $server_port, $ClientPackages_key,
87 $arp_activ, $gosa_unit_tag,
88 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
104 # Where should new systems be placed
105 our $new_systems_ou;
107 # specifies the verbosity of the daemon_log
108 $verbose = 0 ;
110 # if foreground is not null, script will be not forked to background
111 $foreground = 0 ;
113 # specifies the timeout seconds while checking the online status of a registrating client
114 $ping_timeout = 5;
116 $no_bus = 0;
117 $bus_activ = "true";
118 $no_arp = 0;
119 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
120 my @packages_list_statements;
121 my $watch_for_new_jobs_in_progress = 0;
123 # holds all incoming decrypted messages
124 our $incoming_db;
125 our $incoming_tn = 'incoming';
126 my $incoming_file_name;
127 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
128 "timestamp DEFAULT 'none'",
129 "headertag DEFAULT 'none'",
130 "targettag DEFAULT 'none'",
131 "xmlmessage DEFAULT 'none'",
132 "module DEFAULT 'none'",
133 "sessionid DEFAULT '0'",
134 );
136 # holds all gosa jobs
137 our $job_db;
138 our $job_queue_tn = 'jobs';
139 my $job_queue_file_name;
140 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
141 "timestamp DEFAULT 'none'",
142 "status DEFAULT 'none'",
143 "result DEFAULT 'none'",
144 "progress DEFAULT 'none'",
145 "headertag DEFAULT 'none'",
146 "targettag DEFAULT 'none'",
147 "xmlmessage DEFAULT 'none'",
148 "macaddress DEFAULT 'none'",
149 "plainname DEFAULT 'none'",
150 );
152 # holds all other gosa-sd as well as the gosa-sd-bus
153 our $known_server_db;
154 our $known_server_tn = "known_server";
155 my $known_server_file_name;
156 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
158 # holds all registrated clients
159 our $known_clients_db;
160 our $known_clients_tn = "known_clients";
161 my $known_clients_file_name;
162 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
164 # holds all registered clients at a foreign server
165 our $foreign_clients_db;
166 our $foreign_clients_tn = "foreign_clients";
167 my $foreign_clients_file_name;
168 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
170 # holds all logged in user at each client
171 our $login_users_db;
172 our $login_users_tn = "login_users";
173 my $login_users_file_name;
174 my @login_users_col_names = ("client", "user", "timestamp");
176 # holds all fai server, the debian release and tag
177 our $fai_server_db;
178 our $fai_server_tn = "fai_server";
179 my $fai_server_file_name;
180 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
182 our $fai_release_db;
183 our $fai_release_tn = "fai_release";
184 my $fai_release_file_name;
185 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
187 # holds all packages available from different repositories
188 our $packages_list_db;
189 our $packages_list_tn = "packages_list";
190 my $packages_list_file_name;
191 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
192 my $outdir = "/tmp/packages_list_db";
193 my $arch = "i386";
195 # holds all messages which should be delivered to a user
196 our $messaging_db;
197 our $messaging_tn = "messaging";
198 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
199 "flag", "direction", "delivery_time", "message", "timestamp" );
200 my $messaging_file_name;
202 # path to directory to store client install log files
203 our $client_fai_log_dir = "/var/log/fai";
205 # queue which stores taskes until one of the $max_children children are ready to process the task
206 my @tasks = qw();
207 my @msgs_to_decrypt = qw();
208 my $max_children = 2;
211 %cfg_defaults = (
212 "general" => {
213 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
214 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
215 },
216 "bus" => {
217 "activ" => [\$bus_activ, "true"],
218 },
219 "server" => {
220 "port" => [\$server_port, "20081"],
221 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
222 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
223 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
224 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
225 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
226 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
227 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
228 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
229 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
230 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
231 "repo-path" => [\$repo_path, '/srv/www/repository'],
232 "ldap-uri" => [\$ldap_uri, ""],
233 "ldap-base" => [\$ldap_base, ""],
234 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
235 "ldap-admin-password" => [\$ldap_admin_password, ""],
236 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
237 "max-clients" => [\$max_clients, 10],
238 },
239 "GOsaPackages" => {
240 "ip" => [\$gosa_ip, "0.0.0.0"],
241 "port" => [\$gosa_port, "20082"],
242 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
243 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
244 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
245 "key" => [\$GosaPackages_key, "none"],
246 "new-systems-ou" => [\$new_systems_ou, "ou=systems"],
247 },
248 "ClientPackages" => {
249 "key" => [\$ClientPackages_key, "none"],
250 },
251 "ServerPackages"=> {
252 "address" => [\$foreign_server_string, ""],
253 "domain" => [\$server_domain, ""],
254 "key" => [\$ServerPackages_key, "none"],
255 "key-lifetime" => [\$foreign_servers_register_delay, 120],
256 }
257 );
260 #=== FUNCTION ================================================================
261 # NAME: usage
262 # PARAMETERS: nothing
263 # RETURNS: nothing
264 # DESCRIPTION: print out usage text to STDERR
265 #===============================================================================
266 sub usage {
267 print STDERR << "EOF" ;
268 usage: $prg [-hvf] [-c config]
270 -h : this (help) message
271 -c <file> : config file
272 -f : foreground, process will not be forked to background
273 -v : be verbose (multiple to increase verbosity)
274 -no-bus : starts $prg without connection to bus
275 -no-arp : starts $prg without connection to arp module
277 EOF
278 print "\n" ;
279 }
282 #=== FUNCTION ================================================================
283 # NAME: read_configfile
284 # PARAMETERS: cfg_file - string -
285 # RETURNS: nothing
286 # DESCRIPTION: read cfg_file and set variables
287 #===============================================================================
288 sub read_configfile {
289 my $cfg;
290 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
291 if( -r $cfg_file ) {
292 $cfg = Config::IniFiles->new( -file => $cfg_file );
293 } else {
294 print STDERR "Couldn't read config file!\n";
295 }
296 } else {
297 $cfg = Config::IniFiles->new() ;
298 }
299 foreach my $section (keys %cfg_defaults) {
300 foreach my $param (keys %{$cfg_defaults{ $section }}) {
301 my $pinfo = $cfg_defaults{ $section }{ $param };
302 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
303 }
304 }
305 }
308 #=== FUNCTION ================================================================
309 # NAME: logging
310 # PARAMETERS: level - string - default 'info'
311 # msg - string -
312 # facility - string - default 'LOG_DAEMON'
313 # RETURNS: nothing
314 # DESCRIPTION: function for logging
315 #===============================================================================
316 sub daemon_log {
317 # log into log_file
318 my( $msg, $level ) = @_;
319 if(not defined $msg) { return }
320 if(not defined $level) { $level = 1 }
321 if(defined $log_file){
322 open(LOG_HANDLE, ">>$log_file");
323 chmod 0600, $log_file;
324 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
325 print STDERR "cannot open $log_file: $!";
326 return
327 }
328 chomp($msg);
329 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
330 if($level <= $verbose){
331 my ($seconds, $minutes, $hours, $monthday, $month,
332 $year, $weekday, $yearday, $sommertime) = localtime(time);
333 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
334 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
335 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
336 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
337 $month = $monthnames[$month];
338 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
339 $year+=1900;
340 my $name = $prg;
342 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
343 print LOG_HANDLE $log_msg;
344 if( $foreground ) {
345 print STDERR $log_msg;
346 }
347 }
348 close( LOG_HANDLE );
349 }
350 }
353 #=== FUNCTION ================================================================
354 # NAME: check_cmdline_param
355 # PARAMETERS: nothing
356 # RETURNS: nothing
357 # DESCRIPTION: validates commandline parameter
358 #===============================================================================
359 sub check_cmdline_param () {
360 my $err_config;
361 my $err_counter = 0;
362 if(not defined($cfg_file)) {
363 $cfg_file = "/etc/gosa-si/server.conf";
364 if(! -r $cfg_file) {
365 $err_config = "please specify a config file";
366 $err_counter += 1;
367 }
368 }
369 if( $err_counter > 0 ) {
370 &usage( "", 1 );
371 if( defined( $err_config)) { print STDERR "$err_config\n"}
372 print STDERR "\n";
373 exit( -1 );
374 }
375 }
378 #=== FUNCTION ================================================================
379 # NAME: check_pid
380 # PARAMETERS: nothing
381 # RETURNS: nothing
382 # DESCRIPTION: handels pid processing
383 #===============================================================================
384 sub check_pid {
385 $pid = -1;
386 # Check, if we are already running
387 if( open(LOCK_FILE, "<$pid_file") ) {
388 $pid = <LOCK_FILE>;
389 if( defined $pid ) {
390 chomp( $pid );
391 if( -f "/proc/$pid/stat" ) {
392 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
393 if( $stat ) {
394 daemon_log("ERROR: Already running",1);
395 close( LOCK_FILE );
396 exit -1;
397 }
398 }
399 }
400 close( LOCK_FILE );
401 unlink( $pid_file );
402 }
404 # create a syslog msg if it is not to possible to open PID file
405 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
406 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
407 if (open(LOCK_FILE, '<', $pid_file)
408 && ($pid = <LOCK_FILE>))
409 {
410 chomp($pid);
411 $msg .= "(PID $pid)\n";
412 } else {
413 $msg .= "(unable to read PID)\n";
414 }
415 if( ! ($foreground) ) {
416 openlog( $0, "cons,pid", "daemon" );
417 syslog( "warning", $msg );
418 closelog();
419 }
420 else {
421 print( STDERR " $msg " );
422 }
423 exit( -1 );
424 }
425 }
427 #=== FUNCTION ================================================================
428 # NAME: import_modules
429 # PARAMETERS: module_path - string - abs. path to the directory the modules
430 # are stored
431 # RETURNS: nothing
432 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
433 # state is on is imported by "require 'file';"
434 #===============================================================================
435 sub import_modules {
436 daemon_log(" ", 1);
438 if (not -e $modules_path) {
439 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
440 }
442 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
443 while (defined (my $file = readdir (DIR))) {
444 if (not $file =~ /(\S*?).pm$/) {
445 next;
446 }
447 my $mod_name = $1;
449 if( $file =~ /ArpHandler.pm/ ) {
450 if( $no_arp > 0 ) {
451 next;
452 }
453 }
455 eval { require $file; };
456 if ($@) {
457 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
458 daemon_log("$@", 5);
459 } else {
460 my $info = eval($mod_name.'::get_module_info()');
461 # Only load module if get_module_info() returns a non-null object
462 if( $info ) {
463 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
464 $known_modules->{$mod_name} = $info;
465 daemon_log("0 INFO: module $mod_name loaded", 5);
466 }
467 }
468 }
469 close (DIR);
470 }
473 #=== FUNCTION ================================================================
474 # NAME: sig_int_handler
475 # PARAMETERS: signal - string - signal arose from system
476 # RETURNS: noting
477 # DESCRIPTION: handels tasks to be done befor signal becomes active
478 #===============================================================================
479 sub sig_int_handler {
480 my ($signal) = @_;
482 # if (defined($ldap_handle)) {
483 # $ldap_handle->disconnect;
484 # }
485 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
488 daemon_log("shutting down gosa-si-server", 1);
489 system("kill `ps -C gosa-si-server -o pid=`");
490 }
491 $SIG{INT} = \&sig_int_handler;
494 sub check_key_and_xml_validity {
495 my ($crypted_msg, $module_key, $session_id) = @_;
496 my $msg;
497 my $msg_hash;
498 my $error_string;
499 eval{
500 $msg = &decrypt_msg($crypted_msg, $module_key);
502 if ($msg =~ /<xml>/i){
503 $msg =~ s/\s+/ /g; # just for better daemon_log
504 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
505 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
507 ##############
508 # check header
509 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
510 my $header_l = $msg_hash->{'header'};
511 if( 1 > @{$header_l} ) { die 'empty header tag'; }
512 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
513 my $header = @{$header_l}[0];
514 if( 0 == length $header) { die 'empty string in header tag'; }
516 ##############
517 # check source
518 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
519 my $source_l = $msg_hash->{'source'};
520 if( 1 > @{$source_l} ) { die 'empty source tag'; }
521 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
522 my $source = @{$source_l}[0];
523 if( 0 == length $source) { die 'source error'; }
525 ##############
526 # check target
527 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
528 my $target_l = $msg_hash->{'target'};
529 if( 1 > @{$target_l} ) { die 'empty target tag'; }
530 }
531 };
532 if($@) {
533 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
534 $msg = undef;
535 $msg_hash = undef;
536 }
538 return ($msg, $msg_hash);
539 }
542 sub check_outgoing_xml_validity {
543 my ($msg) = @_;
545 my $msg_hash;
546 eval{
547 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
549 ##############
550 # check header
551 my $header_l = $msg_hash->{'header'};
552 if( 1 != @{$header_l} ) {
553 die 'no or more than one headers specified';
554 }
555 my $header = @{$header_l}[0];
556 if( 0 == length $header) {
557 die 'header has length 0';
558 }
560 ##############
561 # check source
562 my $source_l = $msg_hash->{'source'};
563 if( 1 != @{$source_l} ) {
564 die 'no or more than 1 sources specified';
565 }
566 my $source = @{$source_l}[0];
567 if( 0 == length $source) {
568 die 'source has length 0';
569 }
570 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
571 $source =~ /^GOSA$/i ) {
572 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
573 }
575 ##############
576 # check target
577 my $target_l = $msg_hash->{'target'};
578 if( 0 == @{$target_l} ) {
579 die "no targets specified";
580 }
581 foreach my $target (@$target_l) {
582 if( 0 == length $target) {
583 die "target has length 0";
584 }
585 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
586 $target =~ /^GOSA$/i ||
587 $target =~ /^\*$/ ||
588 $target =~ /KNOWN_SERVER/i ||
589 $target =~ /JOBDB/i ||
590 $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 ){
591 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
592 }
593 }
594 };
595 if($@) {
596 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
597 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
598 $msg_hash = undef;
599 }
601 return ($msg_hash);
602 }
605 sub input_from_known_server {
606 my ($input, $remote_ip, $session_id) = @_ ;
607 my ($msg, $msg_hash, $module);
609 my $sql_statement= "SELECT * FROM known_server";
610 my $query_res = $known_server_db->select_dbentry( $sql_statement );
612 while( my ($hit_num, $hit) = each %{ $query_res } ) {
613 my $host_name = $hit->{hostname};
614 if( not $host_name =~ "^$remote_ip") {
615 next;
616 }
617 my $host_key = $hit->{hostkey};
618 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
619 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
621 # check if module can open msg envelope with module key
622 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
623 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
624 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
625 daemon_log("$@", 8);
626 next;
627 }
628 else {
629 $msg = $tmp_msg;
630 $msg_hash = $tmp_msg_hash;
631 $module = "ServerPackages";
632 last;
633 }
634 }
636 if( (!$msg) || (!$msg_hash) || (!$module) ) {
637 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
638 }
640 return ($msg, $msg_hash, $module);
641 }
644 sub input_from_known_client {
645 my ($input, $remote_ip, $session_id) = @_ ;
646 my ($msg, $msg_hash, $module);
648 my $sql_statement= "SELECT * FROM known_clients";
649 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
650 while( my ($hit_num, $hit) = each %{ $query_res } ) {
651 my $host_name = $hit->{hostname};
652 if( not $host_name =~ /^$remote_ip:\d*$/) {
653 next;
654 }
655 my $host_key = $hit->{hostkey};
656 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
657 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
659 # check if module can open msg envelope with module key
660 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
662 if( (!$msg) || (!$msg_hash) ) {
663 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
664 &daemon_log("$@", 8);
665 next;
666 }
667 else {
668 $module = "ClientPackages";
669 last;
670 }
671 }
673 if( (!$msg) || (!$msg_hash) || (!$module) ) {
674 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
675 }
677 return ($msg, $msg_hash, $module);
678 }
681 sub input_from_unknown_host {
682 no strict "refs";
683 my ($input, $session_id) = @_ ;
684 my ($msg, $msg_hash, $module);
685 my $error_string;
687 my %act_modules = %$known_modules;
689 while( my ($mod, $info) = each(%act_modules)) {
691 # check a key exists for this module
692 my $module_key = ${$mod."_key"};
693 if( not defined $module_key ) {
694 if( $mod eq 'ArpHandler' ) {
695 next;
696 }
697 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
698 next;
699 }
700 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
702 # check if module can open msg envelope with module key
703 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
704 if( (not defined $msg) || (not defined $msg_hash) ) {
705 next;
706 }
707 else {
708 $module = $mod;
709 last;
710 }
711 }
713 if( (!$msg) || (!$msg_hash) || (!$module)) {
714 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
715 }
717 return ($msg, $msg_hash, $module);
718 }
721 sub create_ciphering {
722 my ($passwd) = @_;
723 if((!defined($passwd)) || length($passwd)==0) {
724 $passwd = "";
725 }
726 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
727 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
728 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
729 $my_cipher->set_iv($iv);
730 return $my_cipher;
731 }
734 sub encrypt_msg {
735 my ($msg, $key) = @_;
736 my $my_cipher = &create_ciphering($key);
737 my $len;
738 {
739 use bytes;
740 $len= 16-length($msg)%16;
741 }
742 $msg = "\0"x($len).$msg;
743 $msg = $my_cipher->encrypt($msg);
744 chomp($msg = &encode_base64($msg));
745 # there are no newlines allowed inside msg
746 $msg=~ s/\n//g;
747 return $msg;
748 }
751 sub decrypt_msg {
753 my ($msg, $key) = @_ ;
754 $msg = &decode_base64($msg);
755 my $my_cipher = &create_ciphering($key);
756 $msg = $my_cipher->decrypt($msg);
757 $msg =~ s/\0*//g;
758 return $msg;
759 }
762 sub get_encrypt_key {
763 my ($target) = @_ ;
764 my $encrypt_key;
765 my $error = 0;
767 # target can be in known_server
768 if( not defined $encrypt_key ) {
769 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
770 my $query_res = $known_server_db->select_dbentry( $sql_statement );
771 while( my ($hit_num, $hit) = each %{ $query_res } ) {
772 my $host_name = $hit->{hostname};
773 if( $host_name ne $target ) {
774 next;
775 }
776 $encrypt_key = $hit->{hostkey};
777 last;
778 }
779 }
781 # target can be in known_client
782 if( not defined $encrypt_key ) {
783 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
784 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
785 while( my ($hit_num, $hit) = each %{ $query_res } ) {
786 my $host_name = $hit->{hostname};
787 if( $host_name ne $target ) {
788 next;
789 }
790 $encrypt_key = $hit->{hostkey};
791 last;
792 }
793 }
795 return $encrypt_key;
796 }
799 #=== FUNCTION ================================================================
800 # NAME: open_socket
801 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
802 # [PeerPort] string necessary if port not appended by PeerAddr
803 # RETURNS: socket IO::Socket::INET
804 # DESCRIPTION: open a socket to PeerAddr
805 #===============================================================================
806 sub open_socket {
807 my ($PeerAddr, $PeerPort) = @_ ;
808 if(defined($PeerPort)){
809 $PeerAddr = $PeerAddr.":".$PeerPort;
810 }
811 my $socket;
812 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
813 Porto => "tcp",
814 Type => SOCK_STREAM,
815 Timeout => 5,
816 );
817 if(not defined $socket) {
818 return;
819 }
820 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
821 return $socket;
822 }
825 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
826 #=== FUNCTION ================================================================
827 # NAME: get_ip
828 # PARAMETERS: interface name (i.e. eth0)
829 # RETURNS: (ip address)
830 # DESCRIPTION: Uses ioctl to get ip address directly from system.
831 #===============================================================================
832 #sub get_ip {
833 # my $ifreq= shift;
834 # my $result= "";
835 # my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
836 # my $proto= getprotobyname('ip');
837 #
838 # socket SOCKET, PF_INET, SOCK_DGRAM, $proto
839 # or die "socket: $!";
840 #
841 # if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
842 # my ($if, $sin) = unpack 'a16 a16', $ifreq;
843 # my ($port, $addr) = sockaddr_in $sin;
844 # my $ip = inet_ntoa $addr;
845 #
846 # if ($ip && length($ip) > 0) {
847 # $result = $ip;
848 # }
849 # }
850 #
851 # return $result;
852 #}
855 sub get_local_ip_for_remote_ip {
856 my $remote_ip= shift;
857 my $result="0.0.0.0";
859 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
860 if($remote_ip eq "127.0.0.1") {
861 $result = "127.0.0.1";
862 } else {
863 my $PROC_NET_ROUTE= ('/proc/net/route');
865 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
866 or die "Could not open $PROC_NET_ROUTE";
868 my @ifs = <PROC_NET_ROUTE>;
870 close(PROC_NET_ROUTE);
872 # Eat header line
873 shift @ifs;
874 chomp @ifs;
875 foreach my $line(@ifs) {
876 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
877 my $destination;
878 my $mask;
879 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
880 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
881 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
882 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
883 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
884 # destination matches route, save mac and exit
885 $result= &get_ip($Iface);
886 last;
887 }
888 }
889 }
890 } else {
891 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
892 }
893 return $result;
894 }
897 sub send_msg_to_target {
898 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
899 my $error = 0;
900 my $header;
901 my $timestamp = &get_time();
902 my $new_status;
903 my $act_status;
904 my ($sql_statement, $res);
906 if( $msg_header ) {
907 $header = "'$msg_header'-";
908 } else {
909 $header = "";
910 }
912 # Patch the source ip
913 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
914 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
915 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
916 }
918 # encrypt xml msg
919 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
921 # opensocket
922 my $socket = &open_socket($address);
923 if( !$socket ) {
924 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
925 $error++;
926 }
928 if( $error == 0 ) {
929 # send xml msg
930 print $socket $crypted_msg."\n";
932 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
933 daemon_log("$session_id DEBUG: message:\n$msg", 9);
935 }
937 # close socket in any case
938 if( $socket ) {
939 close $socket;
940 }
942 if( $error > 0 ) { $new_status = "down"; }
943 else { $new_status = $msg_header; }
946 # known_clients
947 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
948 $res = $known_clients_db->select_dbentry($sql_statement);
949 if( keys(%$res) == 1) {
950 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
951 if ($act_status eq "down" && $new_status eq "down") {
952 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
953 $res = $known_clients_db->del_dbentry($sql_statement);
954 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
955 } else {
956 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
957 $res = $known_clients_db->update_dbentry($sql_statement);
958 if($new_status eq "down"){
959 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
960 } else {
961 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
962 }
963 }
964 }
966 # known_server
967 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
968 $res = $known_server_db->select_dbentry($sql_statement);
969 if( keys(%$res) == 1) {
970 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
971 if ($act_status eq "down" && $new_status eq "down") {
972 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
973 $res = $known_server_db->del_dbentry($sql_statement);
974 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
975 }
976 else {
977 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
978 $res = $known_server_db->update_dbentry($sql_statement);
979 if($new_status eq "down"){
980 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
981 } else {
982 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
983 }
984 }
985 }
986 return $error;
987 }
990 sub update_jobdb_status_for_send_msgs {
991 my ($answer, $error) = @_;
992 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
993 my $jobdb_id = $1;
995 # sending msg faild
996 if( $error ) {
997 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
998 my $sql_statement = "UPDATE $job_queue_tn ".
999 "SET status='error', result='can not deliver msg, please consult log file' ".
1000 "WHERE id=$jobdb_id";
1001 my $res = $job_db->update_dbentry($sql_statement);
1002 }
1004 # sending msg was successful
1005 } else {
1006 my $sql_statement = "UPDATE $job_queue_tn ".
1007 "SET status='done' ".
1008 "WHERE id=$jobdb_id AND status='processed'";
1009 my $res = $job_db->update_dbentry($sql_statement);
1010 }
1011 }
1012 }
1015 sub sig_handler {
1016 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1017 daemon_log("0 INFO got signal '$signal'", 1);
1018 $kernel->sig_handled();
1019 return;
1020 }
1023 sub msg_to_decrypt {
1024 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1025 my $session_id = $session->ID;
1026 my ($msg, $msg_hash, $module);
1027 my $error = 0;
1029 # hole neue msg aus @msgs_to_decrypt
1030 my $next_msg = shift @msgs_to_decrypt;
1032 # entschlüssle sie
1034 # msg is from a new client or gosa
1035 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1036 # msg is from a gosa-si-server or gosa-si-bus
1037 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1039 }
1040 # msg is from a gosa-si-client
1041 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1043 }
1044 # an error occurred
1045 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1046 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1047 # could not understand a msg from its server the client cause a re-registering process
1048 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1049 "' to cause a re-registering of the client if necessary", 5);
1050 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1051 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1052 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1053 my $host_name = $hit->{'hostname'};
1054 my $host_key = $hit->{'hostkey'};
1055 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1056 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1057 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1058 }
1059 $error++;
1060 }
1063 my $header;
1064 my $target;
1065 my $source;
1066 my $done = 0;
1067 my $sql;
1068 my $res;
1069 # check whether this message should be processed here
1070 if ($error == 0) {
1071 $header = @{$msg_hash->{'header'}}[0];
1072 $target = @{$msg_hash->{'target'}}[0];
1073 $source = @{$msg_hash->{'source'}}[0];
1074 my ($target_ip, $target_port) = split(':', $target);
1075 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1076 my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1077 }
1079 # target and source is equal to GOSA -> process here
1080 if (not $done) {
1081 if ($target eq "GOSA" && $source eq "GOSA") {
1082 $done = 1;
1083 }
1084 }
1086 # target is own address without forward_to_gosa-tag -> process here
1087 if (not $done) {
1088 if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1089 $done = 1;
1090 if ($source eq "GOSA") {
1091 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1092 }
1093 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1094 }
1095 }
1097 # target is a client address in known_clients -> process here
1098 if (not $done) {
1099 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1100 $res = $known_clients_db->select_dbentry($sql);
1101 if (keys(%$res) > 0) {
1102 $done = 1;
1103 my $hostname = $res->{1}->{'hostname'};
1104 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1105 print STDERR "target is a client address in known_clients -> process here\n";
1106 }
1107 }
1109 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1110 if (not $done) {
1111 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1112 my $gosa_at;
1113 my $gosa_session_id;
1114 if (($target eq $server_address) && (defined $forward_to_gosa)){
1115 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1116 if ($gosa_at ne $server_address) {
1117 $done = 1;
1118 print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1119 }
1120 }
1121 }
1123 # if message should be processed here -> add message to incoming_db
1124 if ($done) {
1126 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1127 # so gosa-si-server knows how to process this kind of messages
1128 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1129 $module = "GosaPackages";
1130 }
1132 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1133 primkey=>[],
1134 headertag=>$header,
1135 targettag=>$target,
1136 xmlmessage=>&encode_base64($msg),
1137 timestamp=>&get_time,
1138 module=>$module,
1139 sessionid=>$session_id,
1140 } );
1142 }
1144 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1145 if (not $done) {
1146 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1147 my $gosa_at;
1148 my $gosa_session_id;
1149 if (($target eq $server_address) && (defined $forward_to_gosa)){
1150 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1151 if ($gosa_at eq $server_address) {
1152 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1153 if( defined $session_reference ) {
1154 $heap = $session_reference->get_heap();
1155 }
1156 if(exists $heap->{'client'}) {
1157 $msg = &encrypt_msg($msg, $GosaPackages_key);
1158 $heap->{'client'}->put($msg);
1159 }
1160 $done = 1;
1161 print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1162 }
1163 }
1165 }
1167 # target is a client address in foreign_clients -> forward to registration server
1168 if (not $done) {
1169 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1170 $res = $foreign_clients_db->select_dbentry($sql);
1171 if (keys(%$res) > 0) {
1172 my $hostname = $res->{1}->{'hostname'};
1173 my $regserver = $res->{1}->{'regserver'};
1174 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1175 my $res = $known_server_db->select_dbentry($sql);
1176 if (keys(%$res) > 0) {
1177 my $regserver_key = $res->{1}->{'hostkey'};
1178 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1179 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1180 if ($source eq "GOSA") {
1181 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1182 }
1183 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1184 }
1185 $done = 1;
1186 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1187 }
1188 }
1190 # target is a server address -> forward to server
1191 if (not $done) {
1192 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1193 $res = $known_server_db->select_dbentry($sql);
1194 if (keys(%$res) > 0) {
1195 my $hostkey = $res->{1}->{'hostkey'};
1197 if ($source eq "GOSA") {
1198 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1199 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1201 }
1203 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1204 $done = 1;
1205 print STDERR "target is a server address -> forward to server\n";
1206 }
1209 }
1211 if (not $done) {
1212 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1213 }
1214 }
1216 return;
1217 }
1220 sub next_task {
1221 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1222 my $running_task = POE::Wheel::Run->new(
1223 Program => sub { process_task($session, $heap, $task) },
1224 StdioFilter => POE::Filter::Reference->new(),
1225 StdoutEvent => "task_result",
1226 StderrEvent => "task_debug",
1227 CloseEvent => "task_done",
1228 );
1229 $heap->{task}->{ $running_task->ID } = $running_task;
1230 }
1232 sub handle_task_result {
1233 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1234 my $client_answer = $result->{'answer'};
1235 if( $client_answer =~ s/session_id=(\d+)$// ) {
1236 my $session_id = $1;
1237 if( defined $session_id ) {
1238 my $session_reference = $kernel->ID_id_to_session($session_id);
1239 if( defined $session_reference ) {
1240 $heap = $session_reference->get_heap();
1241 }
1242 }
1244 if(exists $heap->{'client'}) {
1245 $heap->{'client'}->put($client_answer);
1246 }
1247 }
1248 $kernel->sig(CHLD => "child_reap");
1249 }
1251 sub handle_task_debug {
1252 my $result = $_[ARG0];
1253 print STDERR "$result\n";
1254 }
1256 sub handle_task_done {
1257 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1258 delete $heap->{task}->{$task_id};
1259 }
1261 sub process_task {
1262 no strict "refs";
1263 my ($session, $heap, $task) = @_;
1264 my $error = 0;
1265 my $answer_l;
1266 my ($answer_header, @answer_target_l, $answer_source);
1267 my $client_answer = "";
1269 # prepare all variables needed to process message
1270 my $msg = &decode_base64($task->{'xmlmessage'});
1271 my $incoming_id = $task->{'id'};
1272 my $module = $task->{'module'};
1273 my $header = $task->{'headertag'};
1274 my $session_id = $task->{'sessionid'};
1275 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1276 my $source = @{$msg_hash->{'source'}}[0];
1278 # set timestamp of incoming client uptodate, so client will not
1279 # be deleted from known_clients because of expiration
1280 my $act_time = &get_time();
1281 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1282 my $res = $known_clients_db->exec_statement($sql);
1284 ######################
1285 # process incoming msg
1286 if( $error == 0) {
1287 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1288 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1289 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1291 if ( 0 < @{$answer_l} ) {
1292 my $answer_str = join("\n", @{$answer_l});
1293 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1294 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1295 }
1296 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1297 } else {
1298 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1299 }
1301 }
1302 if( !$answer_l ) { $error++ };
1304 ########
1305 # answer
1306 if( $error == 0 ) {
1308 foreach my $answer ( @{$answer_l} ) {
1309 # check outgoing msg to xml validity
1310 my $answer_hash = &check_outgoing_xml_validity($answer);
1311 if( not defined $answer_hash ) { next; }
1313 $answer_header = @{$answer_hash->{'header'}}[0];
1314 @answer_target_l = @{$answer_hash->{'target'}};
1315 $answer_source = @{$answer_hash->{'source'}}[0];
1317 # deliver msg to all targets
1318 foreach my $answer_target ( @answer_target_l ) {
1320 # targets of msg are all gosa-si-clients in known_clients_db
1321 if( $answer_target eq "*" ) {
1322 # answer is for all clients
1323 my $sql_statement= "SELECT * FROM known_clients";
1324 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1325 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1326 my $host_name = $hit->{hostname};
1327 my $host_key = $hit->{hostkey};
1328 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1329 &update_jobdb_status_for_send_msgs($answer, $error);
1330 }
1331 }
1333 # targets of msg are all gosa-si-server in known_server_db
1334 elsif( $answer_target eq "KNOWN_SERVER" ) {
1335 # answer is for all server in known_server
1336 my $sql_statement= "SELECT * FROM $known_server_tn";
1337 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1338 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1339 my $host_name = $hit->{hostname};
1340 my $host_key = $hit->{hostkey};
1341 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1342 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1343 &update_jobdb_status_for_send_msgs($answer, $error);
1344 }
1345 }
1347 # target of msg is GOsa
1348 elsif( $answer_target eq "GOSA" ) {
1349 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1350 my $add_on = "";
1351 if( defined $session_id ) {
1352 $add_on = ".session_id=$session_id";
1353 }
1354 # answer is for GOSA and has to returned to connected client
1355 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1356 $client_answer = $gosa_answer.$add_on;
1357 }
1359 # target of msg is job queue at this host
1360 elsif( $answer_target eq "JOBDB") {
1361 $answer =~ /<header>(\S+)<\/header>/;
1362 my $header;
1363 if( defined $1 ) { $header = $1; }
1364 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1365 &update_jobdb_status_for_send_msgs($answer, $error);
1366 }
1368 # target of msg is a mac address
1369 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 ) {
1370 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1371 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1372 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1373 my $found_ip_flag = 0;
1374 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1375 my $host_name = $hit->{hostname};
1376 my $host_key = $hit->{hostkey};
1377 $answer =~ s/$answer_target/$host_name/g;
1378 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1379 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1380 &update_jobdb_status_for_send_msgs($answer, $error);
1381 $found_ip_flag++ ;
1382 }
1383 if( $found_ip_flag == 0) {
1384 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1385 if( $bus_activ eq "true" ) {
1386 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1387 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1388 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1389 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1390 my $bus_address = $hit->{hostname};
1391 my $bus_key = $hit->{hostkey};
1392 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1393 &update_jobdb_status_for_send_msgs($answer, $error);
1394 last;
1395 }
1396 }
1398 }
1400 # answer is for one specific host
1401 } else {
1402 # get encrypt_key
1403 my $encrypt_key = &get_encrypt_key($answer_target);
1404 if( not defined $encrypt_key ) {
1405 # unknown target, forward msg to bus
1406 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1407 if( $bus_activ eq "true" ) {
1408 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1409 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1410 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1411 my $res_length = keys( %{$query_res} );
1412 if( $res_length == 0 ){
1413 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1414 "no bus found in known_server", 3);
1415 }
1416 else {
1417 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1418 my $bus_key = $hit->{hostkey};
1419 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1420 &update_jobdb_status_for_send_msgs($answer, $error);
1421 }
1422 }
1423 }
1424 next;
1425 }
1426 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1427 &update_jobdb_status_for_send_msgs($answer, $error);
1428 }
1429 }
1430 }
1431 }
1433 my $filter = POE::Filter::Reference->new();
1434 my %result = (
1435 status => "seems ok to me",
1436 answer => $client_answer,
1437 );
1439 my $output = $filter->put( [ \%result ] );
1440 print @$output;
1443 }
1445 sub session_start {
1446 my ($kernel) = $_[KERNEL];
1447 &trigger_db_loop($kernel);
1448 $global_kernel = $kernel;
1449 $kernel->yield('register_at_foreign_servers');
1450 $kernel->yield('create_fai_server_db', $fai_server_tn );
1451 $kernel->yield('create_fai_release_db', $fai_release_tn );
1452 $kernel->yield('watch_for_next_tasks');
1453 $kernel->sig(USR1 => "sig_handler");
1454 $kernel->sig(USR2 => "create_packages_list_db");
1455 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1456 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1457 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1458 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1459 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1460 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1462 }
1464 sub trigger_db_loop {
1465 my ($kernel) = @_ ;
1466 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1467 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1468 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1469 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1470 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1471 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1472 }
1475 sub watch_for_done_jobs {
1476 my ($kernel,$heap) = @_[KERNEL, HEAP];
1478 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1479 my $res = $job_db->select_dbentry( $sql_statement );
1481 while( my ($id, $hit) = each %{$res} ) {
1482 my $jobdb_id = $hit->{id};
1483 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1484 my $res = $job_db->del_dbentry($sql_statement);
1485 }
1487 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1488 }
1491 sub watch_for_new_jobs {
1492 if($watch_for_new_jobs_in_progress == 0) {
1493 $watch_for_new_jobs_in_progress = 1;
1494 my ($kernel,$heap) = @_[KERNEL, HEAP];
1496 # check gosa job queue for jobs with executable timestamp
1497 my $timestamp = &get_time();
1498 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1499 my $res = $job_db->exec_statement( $sql_statement );
1501 # Merge all new jobs that would do the same actions
1502 my @drops;
1503 my $hits;
1504 foreach my $hit (reverse @{$res} ) {
1505 my $macaddress= lc @{$hit}[8];
1506 my $headertag= @{$hit}[5];
1507 if(
1508 defined($hits->{$macaddress}) &&
1509 defined($hits->{$macaddress}->{$headertag}) &&
1510 defined($hits->{$macaddress}->{$headertag}[0])
1511 ) {
1512 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1513 }
1514 $hits->{$macaddress}->{$headertag}= $hit;
1515 }
1517 # Delete new jobs with a matching job in state 'processing'
1518 foreach my $macaddress (keys %{$hits}) {
1519 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1520 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1521 if(defined($jobdb_id)) {
1522 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1523 my $res = $job_db->exec_statement( $sql_statement );
1524 foreach my $hit (@{$res}) {
1525 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1526 }
1527 } else {
1528 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1529 }
1530 }
1531 }
1533 # Commit deletion
1534 $job_db->exec_statementlist(\@drops);
1536 # Look for new jobs that could be executed
1537 foreach my $macaddress (keys %{$hits}) {
1539 # Look if there is an executing job
1540 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1541 my $res = $job_db->exec_statement( $sql_statement );
1543 # Skip new jobs for host if there is a processing job
1544 if(defined($res) and defined @{$res}[0]) {
1545 next;
1546 }
1548 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1549 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1550 if(defined($jobdb_id)) {
1551 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1553 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1554 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1555 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1557 # expect macaddress is unique!!!!!!
1558 my $target = $res_hash->{1}->{hostname};
1560 # change header
1561 $job_msg =~ s/<header>job_/<header>gosa_/;
1563 # add sqlite_id
1564 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1566 $job_msg =~ /<header>(\S+)<\/header>/;
1567 my $header = $1 ;
1568 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1570 # update status in job queue to 'processing'
1571 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1572 my $res = $job_db->update_dbentry($sql_statement);
1573 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1575 # We don't want parallel processing
1576 last;
1577 }
1578 }
1579 }
1581 $watch_for_new_jobs_in_progress = 0;
1582 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1583 }
1584 }
1587 sub watch_for_new_messages {
1588 my ($kernel,$heap) = @_[KERNEL, HEAP];
1589 my @coll_user_msg; # collection list of outgoing messages
1591 # check messaging_db for new incoming messages with executable timestamp
1592 my $timestamp = &get_time();
1593 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1594 my $res = $messaging_db->exec_statement( $sql_statement );
1595 foreach my $hit (@{$res}) {
1597 # create outgoing messages
1598 my $message_to = @{$hit}[3];
1599 # translate message_to to plain login name
1600 my @message_to_l = split(/,/, $message_to);
1601 my %receiver_h;
1602 foreach my $receiver (@message_to_l) {
1603 if ($receiver =~ /^u_([\s\S]*)$/) {
1604 $receiver_h{$1} = 0;
1605 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1606 my $group_name = $1;
1607 # fetch all group members from ldap and add them to receiver hash
1608 my $ldap_handle = &get_ldap_handle();
1609 if (defined $ldap_handle) {
1610 my $mesg = $ldap_handle->search(
1611 base => $ldap_base,
1612 scope => 'sub',
1613 attrs => ['memberUid'],
1614 filter => "cn=$group_name",
1615 );
1616 if ($mesg->count) {
1617 my @entries = $mesg->entries;
1618 foreach my $entry (@entries) {
1619 my @receivers= $entry->get_value("memberUid");
1620 foreach my $receiver (@receivers) {
1621 $receiver_h{$1} = 0;
1622 }
1623 }
1624 }
1625 # translating errors ?
1626 if ($mesg->code) {
1627 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1628 }
1629 # ldap handle error ?
1630 } else {
1631 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1632 }
1633 } else {
1634 my $sbjct = &encode_base64(@{$hit}[1]);
1635 my $msg = &encode_base64(@{$hit}[7]);
1636 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1637 }
1638 }
1639 my @receiver_l = keys(%receiver_h);
1641 my $message_id = @{$hit}[0];
1643 #add each outgoing msg to messaging_db
1644 my $receiver;
1645 foreach $receiver (@receiver_l) {
1646 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1647 "VALUES ('".
1648 $message_id."', '". # id
1649 @{$hit}[1]."', '". # subject
1650 @{$hit}[2]."', '". # message_from
1651 $receiver."', '". # message_to
1652 "none"."', '". # flag
1653 "out"."', '". # direction
1654 @{$hit}[6]."', '". # delivery_time
1655 @{$hit}[7]."', '". # message
1656 $timestamp."'". # timestamp
1657 ")";
1658 &daemon_log("M DEBUG: $sql_statement", 1);
1659 my $res = $messaging_db->exec_statement($sql_statement);
1660 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1661 }
1663 # set incoming message to flag d=deliverd
1664 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1665 &daemon_log("M DEBUG: $sql_statement", 7);
1666 $res = $messaging_db->update_dbentry($sql_statement);
1667 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1668 }
1670 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1671 return;
1672 }
1674 sub watch_for_delivery_messages {
1675 my ($kernel, $heap) = @_[KERNEL, HEAP];
1677 # select outgoing messages
1678 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1679 #&daemon_log("0 DEBUG: $sql", 7);
1680 my $res = $messaging_db->exec_statement( $sql_statement );
1682 # build out msg for each usr
1683 foreach my $hit (@{$res}) {
1684 my $receiver = @{$hit}[3];
1685 my $msg_id = @{$hit}[0];
1686 my $subject = @{$hit}[1];
1687 my $message = @{$hit}[7];
1689 # resolve usr -> host where usr is logged in
1690 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1691 #&daemon_log("0 DEBUG: $sql", 7);
1692 my $res = $login_users_db->exec_statement($sql);
1694 # reciver is logged in nowhere
1695 if (not ref(@$res[0]) eq "ARRAY") { next; }
1697 my $send_succeed = 0;
1698 foreach my $hit (@$res) {
1699 my $receiver_host = @$hit[0];
1700 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1702 # fetch key to encrypt msg propperly for usr/host
1703 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1704 &daemon_log("0 DEBUG: $sql", 7);
1705 my $res = $known_clients_db->select_dbentry($sql);
1707 # host is already down
1708 if (not ref(@$res[0]) eq "ARRAY") { next; }
1710 # host is on
1711 my $receiver_key = @{@{$res}[0]}[2];
1712 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1713 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1714 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1715 if ($error == 0 ) {
1716 $send_succeed++ ;
1717 }
1718 }
1720 if ($send_succeed) {
1721 # set outgoing msg at db to deliverd
1722 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1723 &daemon_log("0 DEBUG: $sql", 7);
1724 my $res = $messaging_db->exec_statement($sql);
1725 }
1726 }
1728 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1729 return;
1730 }
1733 sub watch_for_done_messages {
1734 my ($kernel,$heap) = @_[KERNEL, HEAP];
1736 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1737 #&daemon_log("0 DEBUG: $sql", 7);
1738 my $res = $messaging_db->exec_statement($sql);
1740 foreach my $hit (@{$res}) {
1741 my $msg_id = @{$hit}[0];
1743 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1744 #&daemon_log("0 DEBUG: $sql", 7);
1745 my $res = $messaging_db->exec_statement($sql);
1747 # not all usr msgs have been seen till now
1748 if ( ref(@$res[0]) eq "ARRAY") { next; }
1750 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1751 #&daemon_log("0 DEBUG: $sql", 7);
1752 $res = $messaging_db->exec_statement($sql);
1754 }
1756 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1757 return;
1758 }
1761 sub watch_for_old_known_clients {
1762 my ($kernel,$heap) = @_[KERNEL, HEAP];
1764 my $sql_statement = "SELECT * FROM $known_clients_tn";
1765 my $res = $known_clients_db->select_dbentry( $sql_statement );
1767 my $act_time = int(&get_time());
1769 while ( my ($hit_num, $hit) = each %$res) {
1770 my $expired_timestamp = int($hit->{'timestamp'});
1771 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1772 my $dt = DateTime->new( year => $1,
1773 month => $2,
1774 day => $3,
1775 hour => $4,
1776 minute => $5,
1777 second => $6,
1778 );
1780 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1781 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1782 if ($act_time > $expired_timestamp) {
1783 my $hostname = $hit->{'hostname'};
1784 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1785 my $del_res = $known_clients_db->exec_statement($del_sql);
1787 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1788 }
1790 }
1792 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1793 }
1796 sub watch_for_next_tasks {
1797 my ($kernel,$heap) = @_[KERNEL, HEAP];
1799 my $sql = "SELECT * FROM $incoming_tn";
1800 my $res = $incoming_db->select_dbentry($sql);
1802 while ( my ($hit_num, $hit) = each %$res) {
1803 my $headertag = $hit->{'headertag'};
1804 if ($headertag =~ /^answer_(\d+)/) {
1805 # do not start processing, this message is for a still running POE::Wheel
1806 next;
1807 }
1808 my $message_id = $hit->{'id'};
1809 $kernel->yield('next_task', $hit);
1811 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1812 my $res = $incoming_db->exec_statement($sql);
1813 }
1815 $kernel->delay_set('watch_for_next_tasks', 1);
1816 }
1819 sub get_ldap_handle {
1820 my ($session_id) = @_;
1821 my $heap;
1822 my $ldap_handle;
1824 if (not defined $session_id ) { $session_id = 0 };
1825 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1827 if ($session_id == 0) {
1828 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1829 $ldap_handle = Net::LDAP->new( $ldap_uri );
1830 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1832 } else {
1833 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1834 if( defined $session_reference ) {
1835 $heap = $session_reference->get_heap();
1836 }
1838 if (not defined $heap) {
1839 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1840 return;
1841 }
1843 # TODO: This "if" is nonsense, because it doesn't prove that the
1844 # used handle is still valid - or if we've to reconnect...
1845 #if (not exists $heap->{ldap_handle}) {
1846 $ldap_handle = Net::LDAP->new( $ldap_uri );
1847 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1848 $heap->{ldap_handle} = $ldap_handle;
1849 #}
1850 }
1851 return $ldap_handle;
1852 }
1855 sub change_fai_state {
1856 my ($st, $targets, $session_id) = @_;
1857 $session_id = 0 if not defined $session_id;
1858 # Set FAI state to localboot
1859 my %mapActions= (
1860 reboot => '',
1861 update => 'softupdate',
1862 localboot => 'localboot',
1863 reinstall => 'install',
1864 rescan => '',
1865 wake => '',
1866 memcheck => 'memcheck',
1867 sysinfo => 'sysinfo',
1868 install => 'install',
1869 );
1871 # Return if this is unknown
1872 if (!exists $mapActions{ $st }){
1873 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1874 return;
1875 }
1877 my $state= $mapActions{ $st };
1879 my $ldap_handle = &get_ldap_handle($session_id);
1880 if( defined($ldap_handle) ) {
1882 # Build search filter for hosts
1883 my $search= "(&(objectClass=GOhard)";
1884 foreach (@{$targets}){
1885 $search.= "(macAddress=$_)";
1886 }
1887 $search.= ")";
1889 # If there's any host inside of the search string, procress them
1890 if (!($search =~ /macAddress/)){
1891 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1892 return;
1893 }
1895 # Perform search for Unit Tag
1896 my $mesg = $ldap_handle->search(
1897 base => $ldap_base,
1898 scope => 'sub',
1899 attrs => ['dn', 'FAIstate', 'objectClass'],
1900 filter => "$search"
1901 );
1903 if ($mesg->count) {
1904 my @entries = $mesg->entries;
1905 foreach my $entry (@entries) {
1906 # Only modify entry if it is not set to '$state'
1907 if ($entry->get_value("FAIstate") ne "$state"){
1908 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1909 my $result;
1910 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1911 if (exists $tmp{'FAIobject'}){
1912 if ($state eq ''){
1913 $result= $ldap_handle->modify($entry->dn, changes => [
1914 delete => [ FAIstate => [] ] ]);
1915 } else {
1916 $result= $ldap_handle->modify($entry->dn, changes => [
1917 replace => [ FAIstate => $state ] ]);
1918 }
1919 } elsif ($state ne ''){
1920 $result= $ldap_handle->modify($entry->dn, changes => [
1921 add => [ objectClass => 'FAIobject' ],
1922 add => [ FAIstate => $state ] ]);
1923 }
1925 # Errors?
1926 if ($result->code){
1927 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1928 }
1929 } else {
1930 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1931 }
1932 }
1933 }
1934 # if no ldap handle defined
1935 } else {
1936 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1937 }
1939 }
1942 sub change_goto_state {
1943 my ($st, $targets, $session_id) = @_;
1944 $session_id = 0 if not defined $session_id;
1946 # Switch on or off?
1947 my $state= $st eq 'active' ? 'active': 'locked';
1949 my $ldap_handle = &get_ldap_handle($session_id);
1950 if( defined($ldap_handle) ) {
1952 # Build search filter for hosts
1953 my $search= "(&(objectClass=GOhard)";
1954 foreach (@{$targets}){
1955 $search.= "(macAddress=$_)";
1956 }
1957 $search.= ")";
1959 # If there's any host inside of the search string, procress them
1960 if (!($search =~ /macAddress/)){
1961 return;
1962 }
1964 # Perform search for Unit Tag
1965 my $mesg = $ldap_handle->search(
1966 base => $ldap_base,
1967 scope => 'sub',
1968 attrs => ['dn', 'gotoMode'],
1969 filter => "$search"
1970 );
1972 if ($mesg->count) {
1973 my @entries = $mesg->entries;
1974 foreach my $entry (@entries) {
1976 # Only modify entry if it is not set to '$state'
1977 if ($entry->get_value("gotoMode") ne $state){
1979 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1980 my $result;
1981 $result= $ldap_handle->modify($entry->dn, changes => [
1982 replace => [ gotoMode => $state ] ]);
1984 # Errors?
1985 if ($result->code){
1986 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1987 }
1989 }
1990 }
1991 }
1993 }
1994 }
1997 sub run_create_fai_server_db {
1998 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1999 my $session_id = $session->ID;
2000 my $task = POE::Wheel::Run->new(
2001 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2002 StdoutEvent => "session_run_result",
2003 StderrEvent => "session_run_debug",
2004 CloseEvent => "session_run_done",
2005 );
2007 $heap->{task}->{ $task->ID } = $task;
2008 return;
2009 }
2012 sub create_fai_server_db {
2013 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2014 my $result;
2016 if (not defined $session_id) { $session_id = 0; }
2017 my $ldap_handle = &get_ldap_handle();
2018 if(defined($ldap_handle)) {
2019 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2020 my $mesg= $ldap_handle->search(
2021 base => $ldap_base,
2022 scope => 'sub',
2023 attrs => ['FAIrepository', 'gosaUnitTag'],
2024 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2025 );
2026 if($mesg->{'resultCode'} == 0 &&
2027 $mesg->count != 0) {
2028 foreach my $entry (@{$mesg->{entries}}) {
2029 if($entry->exists('FAIrepository')) {
2030 # Add an entry for each Repository configured for server
2031 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2032 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2033 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2034 $result= $fai_server_db->add_dbentry( {
2035 table => $table_name,
2036 primkey => ['server', 'release', 'tag'],
2037 server => $tmp_url,
2038 release => $tmp_release,
2039 sections => $tmp_sections,
2040 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2041 } );
2042 }
2043 }
2044 }
2045 }
2046 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2048 # TODO: Find a way to post the 'create_packages_list_db' event
2049 if(not defined($dont_create_packages_list)) {
2050 &create_packages_list_db(undef, undef, $session_id);
2051 }
2052 }
2054 $ldap_handle->disconnect;
2055 return $result;
2056 }
2059 sub run_create_fai_release_db {
2060 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2061 my $session_id = $session->ID;
2062 my $task = POE::Wheel::Run->new(
2063 Program => sub { &create_fai_release_db($table_name, $session_id) },
2064 StdoutEvent => "session_run_result",
2065 StderrEvent => "session_run_debug",
2066 CloseEvent => "session_run_done",
2067 );
2069 $heap->{task}->{ $task->ID } = $task;
2070 return;
2071 }
2074 sub create_fai_release_db {
2075 my ($table_name, $session_id) = @_;
2076 my $result;
2078 # used for logging
2079 if (not defined $session_id) { $session_id = 0; }
2081 my $ldap_handle = &get_ldap_handle();
2082 if(defined($ldap_handle)) {
2083 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2084 my $mesg= $ldap_handle->search(
2085 base => $ldap_base,
2086 scope => 'sub',
2087 attrs => [],
2088 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2089 );
2090 if($mesg->{'resultCode'} == 0 &&
2091 $mesg->count != 0) {
2092 # Walk through all possible FAI container ou's
2093 my @sql_list;
2094 my $timestamp= &get_time();
2095 foreach my $ou (@{$mesg->{entries}}) {
2096 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2097 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2098 my @tmp_array=get_fai_release_entries($tmp_classes);
2099 if(@tmp_array) {
2100 foreach my $entry (@tmp_array) {
2101 if(defined($entry) && ref($entry) eq 'HASH') {
2102 my $sql=
2103 "INSERT INTO $table_name "
2104 ."(timestamp, release, class, type, state) VALUES ("
2105 .$timestamp.","
2106 ."'".$entry->{'release'}."',"
2107 ."'".$entry->{'class'}."',"
2108 ."'".$entry->{'type'}."',"
2109 ."'".$entry->{'state'}."')";
2110 push @sql_list, $sql;
2111 }
2112 }
2113 }
2114 }
2115 }
2117 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2118 if(@sql_list) {
2119 unshift @sql_list, "VACUUM";
2120 unshift @sql_list, "DELETE FROM $table_name";
2121 $fai_release_db->exec_statementlist(\@sql_list);
2122 }
2123 daemon_log("$session_id DEBUG: Done with inserting",7);
2124 }
2125 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2126 }
2127 $ldap_handle->disconnect;
2128 return $result;
2129 }
2131 sub get_fai_types {
2132 my $tmp_classes = shift || return undef;
2133 my @result;
2135 foreach my $type(keys %{$tmp_classes}) {
2136 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2137 my $entry = {
2138 type => $type,
2139 state => $tmp_classes->{$type}[0],
2140 };
2141 push @result, $entry;
2142 }
2143 }
2145 return @result;
2146 }
2148 sub get_fai_state {
2149 my $result = "";
2150 my $tmp_classes = shift || return $result;
2152 foreach my $type(keys %{$tmp_classes}) {
2153 if(defined($tmp_classes->{$type}[0])) {
2154 $result = $tmp_classes->{$type}[0];
2156 # State is equal for all types in class
2157 last;
2158 }
2159 }
2161 return $result;
2162 }
2164 sub resolve_fai_classes {
2165 my ($fai_base, $ldap_handle, $session_id) = @_;
2166 if (not defined $session_id) { $session_id = 0; }
2167 my $result;
2168 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2169 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2170 my $fai_classes;
2172 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2173 my $mesg= $ldap_handle->search(
2174 base => $fai_base,
2175 scope => 'sub',
2176 attrs => ['cn','objectClass','FAIstate'],
2177 filter => $fai_filter,
2178 );
2179 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2181 if($mesg->{'resultCode'} == 0 &&
2182 $mesg->count != 0) {
2183 foreach my $entry (@{$mesg->{entries}}) {
2184 if($entry->exists('cn')) {
2185 my $tmp_dn= $entry->dn();
2187 # Skip classname and ou dn parts for class
2188 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2190 # Skip classes without releases
2191 if((!defined($tmp_release)) || length($tmp_release)==0) {
2192 next;
2193 }
2195 my $tmp_cn= $entry->get_value('cn');
2196 my $tmp_state= $entry->get_value('FAIstate');
2198 my $tmp_type;
2199 # Get FAI type
2200 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2201 if(grep $_ eq $oclass, @possible_fai_classes) {
2202 $tmp_type= $oclass;
2203 last;
2204 }
2205 }
2207 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2208 # A Subrelease
2209 my @sub_releases = split(/,/, $tmp_release);
2211 # Walk through subreleases and build hash tree
2212 my $hash;
2213 while(my $tmp_sub_release = pop @sub_releases) {
2214 $hash .= "\{'$tmp_sub_release'\}->";
2215 }
2216 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2217 } else {
2218 # A branch, no subrelease
2219 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2220 }
2221 } elsif (!$entry->exists('cn')) {
2222 my $tmp_dn= $entry->dn();
2223 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2225 # Skip classes without releases
2226 if((!defined($tmp_release)) || length($tmp_release)==0) {
2227 next;
2228 }
2230 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2231 # A Subrelease
2232 my @sub_releases= split(/,/, $tmp_release);
2234 # Walk through subreleases and build hash tree
2235 my $hash;
2236 while(my $tmp_sub_release = pop @sub_releases) {
2237 $hash .= "\{'$tmp_sub_release'\}->";
2238 }
2239 # Remove the last two characters
2240 chop($hash);
2241 chop($hash);
2243 eval('$fai_classes->'.$hash.'= {}');
2244 } else {
2245 # A branch, no subrelease
2246 if(!exists($fai_classes->{$tmp_release})) {
2247 $fai_classes->{$tmp_release} = {};
2248 }
2249 }
2250 }
2251 }
2253 # The hash is complete, now we can honor the copy-on-write based missing entries
2254 foreach my $release (keys %$fai_classes) {
2255 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2256 }
2257 }
2258 return $result;
2259 }
2261 sub apply_fai_inheritance {
2262 my $fai_classes = shift || return {};
2263 my $tmp_classes;
2265 # Get the classes from the branch
2266 foreach my $class (keys %{$fai_classes}) {
2267 # Skip subreleases
2268 if($class =~ /^ou=.*$/) {
2269 next;
2270 } else {
2271 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2272 }
2273 }
2275 # Apply to each subrelease
2276 foreach my $subrelease (keys %{$fai_classes}) {
2277 if($subrelease =~ /ou=/) {
2278 foreach my $tmp_class (keys %{$tmp_classes}) {
2279 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2280 $fai_classes->{$subrelease}->{$tmp_class} =
2281 deep_copy($tmp_classes->{$tmp_class});
2282 } else {
2283 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2284 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2285 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2286 deep_copy($tmp_classes->{$tmp_class}->{$type});
2287 }
2288 }
2289 }
2290 }
2291 }
2292 }
2294 # Find subreleases in deeper levels
2295 foreach my $subrelease (keys %{$fai_classes}) {
2296 if($subrelease =~ /ou=/) {
2297 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2298 if($subsubrelease =~ /ou=/) {
2299 apply_fai_inheritance($fai_classes->{$subrelease});
2300 }
2301 }
2302 }
2303 }
2305 return $fai_classes;
2306 }
2308 sub get_fai_release_entries {
2309 my $tmp_classes = shift || return;
2310 my $parent = shift || "";
2311 my @result = shift || ();
2313 foreach my $entry (keys %{$tmp_classes}) {
2314 if(defined($entry)) {
2315 if($entry =~ /^ou=.*$/) {
2316 my $release_name = $entry;
2317 $release_name =~ s/ou=//g;
2318 if(length($parent)>0) {
2319 $release_name = $parent."/".$release_name;
2320 }
2321 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2322 foreach my $bufentry(@bufentries) {
2323 push @result, $bufentry;
2324 }
2325 } else {
2326 my @types = get_fai_types($tmp_classes->{$entry});
2327 foreach my $type (@types) {
2328 push @result,
2329 {
2330 'class' => $entry,
2331 'type' => $type->{'type'},
2332 'release' => $parent,
2333 'state' => $type->{'state'},
2334 };
2335 }
2336 }
2337 }
2338 }
2340 return @result;
2341 }
2343 sub deep_copy {
2344 my $this = shift;
2345 if (not ref $this) {
2346 $this;
2347 } elsif (ref $this eq "ARRAY") {
2348 [map deep_copy($_), @$this];
2349 } elsif (ref $this eq "HASH") {
2350 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2351 } else { die "what type is $_?" }
2352 }
2355 sub session_run_result {
2356 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2357 $kernel->sig(CHLD => "child_reap");
2358 }
2360 sub session_run_debug {
2361 my $result = $_[ARG0];
2362 print STDERR "$result\n";
2363 }
2365 sub session_run_done {
2366 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2367 delete $heap->{task}->{$task_id};
2368 }
2371 sub create_sources_list {
2372 my $session_id = shift;
2373 my $ldap_handle = &main::get_ldap_handle;
2374 my $result="/tmp/gosa_si_tmp_sources_list";
2376 # Remove old file
2377 if(stat($result)) {
2378 unlink($result);
2379 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2380 }
2382 my $fh;
2383 open($fh, ">$result");
2384 if (not defined $fh) {
2385 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2386 return undef;
2387 }
2388 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2389 my $mesg=$ldap_handle->search(
2390 base => $main::ldap_server_dn,
2391 scope => 'base',
2392 attrs => 'FAIrepository',
2393 filter => 'objectClass=FAIrepositoryServer'
2394 );
2395 if($mesg->count) {
2396 foreach my $entry(@{$mesg->{'entries'}}) {
2397 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2398 my ($server, $tag, $release, $sections)= split /\|/, $value;
2399 my $line = "deb $server $release";
2400 $sections =~ s/,/ /g;
2401 $line.= " $sections";
2402 print $fh $line."\n";
2403 }
2404 }
2405 }
2406 } else {
2407 if (defined $main::ldap_server_dn){
2408 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2409 } else {
2410 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2411 }
2412 }
2413 close($fh);
2415 return $result;
2416 }
2419 sub run_create_packages_list_db {
2420 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2421 my $session_id = $session->ID;
2423 my $task = POE::Wheel::Run->new(
2424 Priority => +20,
2425 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2426 StdoutEvent => "session_run_result",
2427 StderrEvent => "session_run_debug",
2428 CloseEvent => "session_run_done",
2429 );
2430 $heap->{task}->{ $task->ID } = $task;
2431 }
2434 sub create_packages_list_db {
2435 my ($ldap_handle, $sources_file, $session_id) = @_;
2437 # it should not be possible to trigger a recreation of packages_list_db
2438 # while packages_list_db is under construction, so set flag packages_list_under_construction
2439 # which is tested befor recreation can be started
2440 if (-r $packages_list_under_construction) {
2441 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2442 return;
2443 } else {
2444 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2445 # set packages_list_under_construction to true
2446 system("touch $packages_list_under_construction");
2447 @packages_list_statements=();
2448 }
2450 if (not defined $session_id) { $session_id = 0; }
2451 if (not defined $ldap_handle) {
2452 $ldap_handle= &get_ldap_handle();
2454 if (not defined $ldap_handle) {
2455 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2456 unlink($packages_list_under_construction);
2457 return;
2458 }
2459 }
2460 if (not defined $sources_file) {
2461 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2462 $sources_file = &create_sources_list($session_id);
2463 }
2465 if (not defined $sources_file) {
2466 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2467 unlink($packages_list_under_construction);
2468 return;
2469 }
2471 my $line;
2473 open(CONFIG, "<$sources_file") or do {
2474 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2475 unlink($packages_list_under_construction);
2476 return;
2477 };
2479 # Read lines
2480 while ($line = <CONFIG>){
2481 # Unify
2482 chop($line);
2483 $line =~ s/^\s+//;
2484 $line =~ s/^\s+/ /;
2486 # Strip comments
2487 $line =~ s/#.*$//g;
2489 # Skip empty lines
2490 if ($line =~ /^\s*$/){
2491 next;
2492 }
2494 # Interpret deb line
2495 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2496 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2497 my $section;
2498 foreach $section (split(' ', $sections)){
2499 &parse_package_info( $baseurl, $dist, $section, $session_id );
2500 }
2501 }
2502 }
2504 close (CONFIG);
2506 find(\&cleanup_and_extract, keys( %repo_dirs ));
2507 &main::strip_packages_list_statements();
2508 unshift @packages_list_statements, "VACUUM";
2509 $packages_list_db->exec_statementlist(\@packages_list_statements);
2510 unlink($packages_list_under_construction);
2511 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2512 return;
2513 }
2515 # This function should do some intensive task to minimize the db-traffic
2516 sub strip_packages_list_statements {
2517 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2518 my @new_statement_list=();
2519 my $hash;
2520 my $insert_hash;
2521 my $update_hash;
2522 my $delete_hash;
2523 my $local_timestamp=get_time();
2525 foreach my $existing_entry (@existing_entries) {
2526 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2527 }
2529 foreach my $statement (@packages_list_statements) {
2530 if($statement =~ /^INSERT/i) {
2531 # Assign the values from the insert statement
2532 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2533 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2534 if(exists($hash->{$distribution}->{$package}->{$version})) {
2535 # If section or description has changed, update the DB
2536 if(
2537 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2538 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2539 ) {
2540 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2541 }
2542 } else {
2543 # Insert a non-existing entry to db
2544 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2545 }
2546 } elsif ($statement =~ /^UPDATE/i) {
2547 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2548 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2549 foreach my $distribution (keys %{$hash}) {
2550 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2551 # update the insertion hash to execute only one query per package (insert instead insert+update)
2552 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2553 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2554 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2555 my $section;
2556 my $description;
2557 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2558 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2559 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2560 }
2561 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2562 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2563 }
2564 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2565 }
2566 }
2567 }
2568 }
2569 }
2571 # TODO: Check for orphaned entries
2573 # unroll the insert_hash
2574 foreach my $distribution (keys %{$insert_hash}) {
2575 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2576 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2577 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2578 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2579 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2580 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2581 ."'$local_timestamp')";
2582 }
2583 }
2584 }
2586 # unroll the update hash
2587 foreach my $distribution (keys %{$update_hash}) {
2588 foreach my $package (keys %{$update_hash->{$distribution}}) {
2589 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2590 my $set = "";
2591 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2592 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2593 }
2594 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2595 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2596 }
2597 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2598 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2599 }
2600 if(defined($set) and length($set) > 0) {
2601 $set .= "timestamp = '$local_timestamp'";
2602 } else {
2603 next;
2604 }
2605 push @new_statement_list,
2606 "UPDATE $main::packages_list_tn SET $set WHERE"
2607 ." distribution = '$distribution'"
2608 ." AND package = '$package'"
2609 ." AND version = '$version'";
2610 }
2611 }
2612 }
2614 @packages_list_statements = @new_statement_list;
2615 }
2618 sub parse_package_info {
2619 my ($baseurl, $dist, $section, $session_id)= @_;
2620 my ($package);
2621 if (not defined $session_id) { $session_id = 0; }
2622 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2623 $repo_dirs{ "${repo_path}/pool" } = 1;
2625 foreach $package ("Packages.gz"){
2626 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2627 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2628 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2629 }
2631 }
2634 sub get_package {
2635 my ($url, $dest, $session_id)= @_;
2636 if (not defined $session_id) { $session_id = 0; }
2638 my $tpath = dirname($dest);
2639 -d "$tpath" || mkpath "$tpath";
2641 # This is ugly, but I've no time to take a look at "how it works in perl"
2642 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2643 system("gunzip -cd '$dest' > '$dest.in'");
2644 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2645 unlink($dest);
2646 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2647 } else {
2648 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2649 }
2650 return 0;
2651 }
2654 sub parse_package {
2655 my ($path, $dist, $srv_path, $session_id)= @_;
2656 if (not defined $session_id) { $session_id = 0;}
2657 my ($package, $version, $section, $description);
2658 my $PACKAGES;
2659 my $timestamp = &get_time();
2661 if(not stat("$path.in")) {
2662 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2663 return;
2664 }
2666 open($PACKAGES, "<$path.in");
2667 if(not defined($PACKAGES)) {
2668 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2669 return;
2670 }
2672 # Read lines
2673 while (<$PACKAGES>){
2674 my $line = $_;
2675 # Unify
2676 chop($line);
2678 # Use empty lines as a trigger
2679 if ($line =~ /^\s*$/){
2680 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2681 push(@packages_list_statements, $sql);
2682 $package = "none";
2683 $version = "none";
2684 $section = "none";
2685 $description = "none";
2686 next;
2687 }
2689 # Trigger for package name
2690 if ($line =~ /^Package:\s/){
2691 ($package)= ($line =~ /^Package: (.*)$/);
2692 next;
2693 }
2695 # Trigger for version
2696 if ($line =~ /^Version:\s/){
2697 ($version)= ($line =~ /^Version: (.*)$/);
2698 next;
2699 }
2701 # Trigger for description
2702 if ($line =~ /^Description:\s/){
2703 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2704 next;
2705 }
2707 # Trigger for section
2708 if ($line =~ /^Section:\s/){
2709 ($section)= ($line =~ /^Section: (.*)$/);
2710 next;
2711 }
2713 # Trigger for filename
2714 if ($line =~ /^Filename:\s/){
2715 my ($filename) = ($line =~ /^Filename: (.*)$/);
2716 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2717 next;
2718 }
2719 }
2721 close( $PACKAGES );
2722 unlink( "$path.in" );
2723 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2724 }
2727 sub store_fileinfo {
2728 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2730 my %fileinfo = (
2731 'package' => $package,
2732 'dist' => $dist,
2733 'version' => $vers,
2734 );
2736 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2737 }
2740 sub cleanup_and_extract {
2741 my $fileinfo = $repo_files{ $File::Find::name };
2743 if( defined $fileinfo ) {
2745 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2746 my $sql;
2747 my $package = $fileinfo->{ 'package' };
2748 my $newver = $fileinfo->{ 'version' };
2750 mkpath($dir);
2751 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2753 if( -f "$dir/DEBIAN/templates" ) {
2755 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2757 my $tmpl= "";
2758 {
2759 local $/=undef;
2760 open FILE, "$dir/DEBIAN/templates";
2761 $tmpl = &encode_base64(<FILE>);
2762 close FILE;
2763 }
2764 rmtree("$dir/DEBIAN/templates");
2766 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2767 push @packages_list_statements, $sql;
2768 }
2769 }
2771 return;
2772 }
2775 sub register_at_foreign_servers {
2776 my ($kernel) = $_[KERNEL];
2778 # hole alle bekannten server aus known_server_db
2779 my $server_sql = "SELECT * FROM $known_server_tn";
2780 my $server_res = $known_server_db->exec_statement($server_sql);
2782 # no entries in known_server_db
2783 if (not ref(@$server_res[0]) eq "ARRAY") {
2784 # TODO
2785 }
2787 # detect already connected clients
2788 my $client_sql = "SELECT * FROM $known_clients_tn";
2789 my $client_res = $known_clients_db->exec_statement($client_sql);
2791 # send my server details to all other gosa-si-server within the network
2792 foreach my $hit (@$server_res) {
2793 my $hostname = @$hit[0];
2794 my $hostkey = &create_passwd;
2796 # add already connected clients to registration message
2797 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2798 &add_content2xml_hash($myhash, 'key', $hostkey);
2799 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2801 # build registration message and send it
2802 my $foreign_server_msg = &create_xml_string($myhash);
2803 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2804 }
2806 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2807 return;
2808 }
2811 #==== MAIN = main ==============================================================
2812 # parse commandline options
2813 Getopt::Long::Configure( "bundling" );
2814 GetOptions("h|help" => \&usage,
2815 "c|config=s" => \$cfg_file,
2816 "f|foreground" => \$foreground,
2817 "v|verbose+" => \$verbose,
2818 "no-bus+" => \$no_bus,
2819 "no-arp+" => \$no_arp,
2820 );
2822 # read and set config parameters
2823 &check_cmdline_param ;
2824 &read_configfile;
2825 &check_pid;
2827 $SIG{CHLD} = 'IGNORE';
2829 # forward error messages to logfile
2830 if( ! $foreground ) {
2831 open( STDIN, '+>/dev/null' );
2832 open( STDOUT, '+>&STDIN' );
2833 open( STDERR, '+>&STDIN' );
2834 }
2836 # Just fork, if we are not in foreground mode
2837 if( ! $foreground ) {
2838 chdir '/' or die "Can't chdir to /: $!";
2839 $pid = fork;
2840 setsid or die "Can't start a new session: $!";
2841 umask 0;
2842 } else {
2843 $pid = $$;
2844 }
2846 # Do something useful - put our PID into the pid_file
2847 if( 0 != $pid ) {
2848 open( LOCK_FILE, ">$pid_file" );
2849 print LOCK_FILE "$pid\n";
2850 close( LOCK_FILE );
2851 if( !$foreground ) {
2852 exit( 0 )
2853 };
2854 }
2856 # parse head url and revision from svn
2857 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2858 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2859 $server_headURL = defined $1 ? $1 : 'unknown' ;
2860 $server_revision = defined $2 ? $2 : 'unknown' ;
2861 if ($server_headURL =~ /\/tag\// ||
2862 $server_headURL =~ /\/branches\// ) {
2863 $server_status = "stable";
2864 } else {
2865 $server_status = "developmental" ;
2866 }
2869 daemon_log(" ", 1);
2870 daemon_log("$0 started!", 1);
2871 daemon_log("status: $server_status", 1);
2872 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2874 if ($no_bus > 0) {
2875 $bus_activ = "false"
2876 }
2878 # connect to incoming_db
2879 unlink($incoming_file_name);
2880 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2881 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2883 # connect to gosa-si job queue
2884 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2885 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2887 # connect to known_clients_db
2888 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2889 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2891 # connect to foreign_clients_db
2892 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2893 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2895 # connect to known_server_db
2896 unlink($known_server_file_name);
2897 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2898 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2900 # connect to login_usr_db
2901 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2902 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2904 # connect to fai_server_db and fai_release_db
2905 unlink($fai_server_file_name);
2906 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2907 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2909 unlink($fai_release_file_name);
2910 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2911 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2913 # connect to packages_list_db
2914 #unlink($packages_list_file_name);
2915 unlink($packages_list_under_construction);
2916 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2917 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2919 # connect to messaging_db
2920 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2921 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2924 # create xml object used for en/decrypting
2925 $xml = new XML::Simple();
2928 # foreign servers
2929 my @foreign_server_list;
2931 # add foreign server from cfg file
2932 if ($foreign_server_string ne "") {
2933 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2934 foreach my $foreign_server (@cfg_foreign_server_list) {
2935 push(@foreign_server_list, $foreign_server);
2936 }
2937 }
2939 # add foreign server from dns
2940 my @tmp_servers;
2941 if ( !$server_domain) {
2942 # Try our DNS Searchlist
2943 for my $domain(get_dns_domains()) {
2944 chomp($domain);
2945 my @tmp_domains= &get_server_addresses($domain);
2946 if(@tmp_domains) {
2947 for my $tmp_server(@tmp_domains) {
2948 push @tmp_servers, $tmp_server;
2949 }
2950 }
2951 }
2952 if(@tmp_servers && length(@tmp_servers)==0) {
2953 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2954 }
2955 } else {
2956 @tmp_servers = &get_server_addresses($server_domain);
2957 if( 0 == @tmp_servers ) {
2958 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2959 }
2960 }
2961 foreach my $server (@tmp_servers) {
2962 unshift(@foreign_server_list, $server);
2963 }
2964 # eliminate duplicate entries
2965 @foreign_server_list = &del_doubles(@foreign_server_list);
2966 my $all_foreign_server = join(", ", @foreign_server_list);
2967 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2969 # add all found foreign servers to known_server
2970 my $act_timestamp = &get_time();
2971 foreach my $foreign_server (@foreign_server_list) {
2972 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2973 primkey=>['hostname'],
2974 hostname=>$foreign_server,
2975 status=>'not_jet_registered',
2976 hostkey=>"none",
2977 timestamp=>$act_timestamp,
2978 } );
2979 }
2982 POE::Component::Server::TCP->new(
2983 Alias => "TCP_SERVER",
2984 Port => $server_port,
2985 ClientInput => sub {
2986 my ($kernel, $input) = @_[KERNEL, ARG0];
2987 push(@tasks, $input);
2988 push(@msgs_to_decrypt, $input);
2989 $kernel->yield("msg_to_decrypt");
2990 },
2991 InlineStates => {
2992 msg_to_decrypt => \&msg_to_decrypt,
2993 next_task => \&next_task,
2994 task_result => \&handle_task_result,
2995 task_done => \&handle_task_done,
2996 task_debug => \&handle_task_debug,
2997 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2998 }
2999 );
3001 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3003 # create session for repeatedly checking the job queue for jobs
3004 POE::Session->create(
3005 inline_states => {
3006 _start => \&session_start,
3007 register_at_foreign_servers => \®ister_at_foreign_servers,
3008 sig_handler => \&sig_handler,
3009 next_task => \&next_task,
3010 task_result => \&handle_task_result,
3011 task_done => \&handle_task_done,
3012 task_debug => \&handle_task_debug,
3013 watch_for_next_tasks => \&watch_for_next_tasks,
3014 watch_for_new_messages => \&watch_for_new_messages,
3015 watch_for_delivery_messages => \&watch_for_delivery_messages,
3016 watch_for_done_messages => \&watch_for_done_messages,
3017 watch_for_new_jobs => \&watch_for_new_jobs,
3018 watch_for_done_jobs => \&watch_for_done_jobs,
3019 watch_for_old_known_clients => \&watch_for_old_known_clients,
3020 create_packages_list_db => \&run_create_packages_list_db,
3021 create_fai_server_db => \&run_create_fai_server_db,
3022 create_fai_release_db => \&run_create_fai_release_db,
3023 session_run_result => \&session_run_result,
3024 session_run_debug => \&session_run_debug,
3025 session_run_done => \&session_run_done,
3026 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3027 }
3028 );
3031 # import all modules
3032 &import_modules;
3034 # TODO
3035 # check wether all modules are gosa-si valid passwd check
3039 POE::Kernel->run();
3040 exit;