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 ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
90 $arp_enabled, $arp_interface,
91 );
93 # additional variable which should be globaly accessable
94 our $server_address;
95 our $server_mac_address;
96 our $gosa_address;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_arp = 0;
113 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
114 my @packages_list_statements;
115 my $watch_for_new_jobs_in_progress = 0;
117 # holds all incoming decrypted messages
118 our $incoming_db;
119 our $incoming_tn = 'incoming';
120 my $incoming_file_name;
121 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
122 "timestamp DEFAULT 'none'",
123 "headertag DEFAULT 'none'",
124 "targettag DEFAULT 'none'",
125 "xmlmessage DEFAULT 'none'",
126 "module DEFAULT 'none'",
127 "sessionid DEFAULT '0'",
128 );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
135 "timestamp DEFAULT 'none'",
136 "status DEFAULT 'none'",
137 "result DEFAULT 'none'",
138 "progress DEFAULT 'none'",
139 "headertag DEFAULT 'none'",
140 "targettag DEFAULT 'none'",
141 "xmlmessage DEFAULT 'none'",
142 "macaddress DEFAULT 'none'",
143 "plainname DEFAULT 'none'",
144 "siserver DEFAULT 'none'",
145 "modified DEFAULT '0'",
146 );
148 # holds all other gosa-si-server
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients";
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server";
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release";
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386";
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging";
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
195 "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai";
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 %cfg_defaults = (
208 "general" => {
209 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
210 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
211 },
212 "server" => {
213 "ip" => [\$server_ip, "0.0.0.0"],
214 "port" => [\$server_port, "20081"],
215 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
216 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
217 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
218 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
219 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
220 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
221 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
222 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
223 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
224 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
225 "repo-path" => [\$repo_path, '/srv/www/repository'],
226 "ldap-uri" => [\$ldap_uri, ""],
227 "ldap-base" => [\$ldap_base, ""],
228 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
229 "ldap-admin-password" => [\$ldap_admin_password, ""],
230 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
231 "max-clients" => [\$max_clients, 10],
232 "wol-password" => [\$wake_on_lan_passwd, ""],
233 },
234 "GOsaPackages" => {
235 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
236 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
237 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
238 "key" => [\$GosaPackages_key, "none"],
239 },
240 "ClientPackages" => {
241 "key" => [\$ClientPackages_key, "none"],
242 },
243 "ServerPackages"=> {
244 "address" => [\$foreign_server_string, ""],
245 "domain" => [\$server_domain, ""],
246 "key" => [\$ServerPackages_key, "none"],
247 "key-lifetime" => [\$foreign_servers_register_delay, 120],
248 "job-synchronization-enabled" => [\$job_synchronization, "true"],
249 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
250 },
251 "ArpHandler" => {
252 "enabled" => [\$arp_enabled, "true"],
253 "interface" => [\$arp_interface, "all"],
254 },
256 );
259 #=== FUNCTION ================================================================
260 # NAME: usage
261 # PARAMETERS: nothing
262 # RETURNS: nothing
263 # DESCRIPTION: print out usage text to STDERR
264 #===============================================================================
265 sub usage {
266 print STDERR << "EOF" ;
267 usage: $prg [-hvf] [-c config]
269 -h : this (help) message
270 -c <file> : config file
271 -f : foreground, process will not be forked to background
272 -v : be verbose (multiple to increase verbosity)
273 -no-arp : starts $prg without connection to arp module
275 EOF
276 print "\n" ;
277 }
280 #=== FUNCTION ================================================================
281 # NAME: read_configfile
282 # PARAMETERS: cfg_file - string -
283 # RETURNS: nothing
284 # DESCRIPTION: read cfg_file and set variables
285 #===============================================================================
286 sub read_configfile {
287 my $cfg;
288 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
289 if( -r $cfg_file ) {
290 $cfg = Config::IniFiles->new( -file => $cfg_file );
291 } else {
292 print STDERR "Couldn't read config file!\n";
293 }
294 } else {
295 $cfg = Config::IniFiles->new() ;
296 }
297 foreach my $section (keys %cfg_defaults) {
298 foreach my $param (keys %{$cfg_defaults{ $section }}) {
299 my $pinfo = $cfg_defaults{ $section }{ $param };
300 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
301 }
302 }
303 }
306 #=== FUNCTION ================================================================
307 # NAME: logging
308 # PARAMETERS: level - string - default 'info'
309 # msg - string -
310 # facility - string - default 'LOG_DAEMON'
311 # RETURNS: nothing
312 # DESCRIPTION: function for logging
313 #===============================================================================
314 sub daemon_log {
315 # log into log_file
316 my( $msg, $level ) = @_;
317 if(not defined $msg) { return }
318 if(not defined $level) { $level = 1 }
319 if(defined $log_file){
320 open(LOG_HANDLE, ">>$log_file");
321 chmod 0600, $log_file;
322 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
323 print STDERR "cannot open $log_file: $!";
324 return
325 }
326 chomp($msg);
327 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
328 if($level <= $verbose){
329 my ($seconds, $minutes, $hours, $monthday, $month,
330 $year, $weekday, $yearday, $sommertime) = localtime(time);
331 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
332 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
333 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
334 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
335 $month = $monthnames[$month];
336 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
337 $year+=1900;
338 my $name = $prg;
340 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
341 print LOG_HANDLE $log_msg;
342 if( $foreground ) {
343 print STDERR $log_msg;
344 }
345 }
346 close( LOG_HANDLE );
347 }
348 }
351 #=== FUNCTION ================================================================
352 # NAME: check_cmdline_param
353 # PARAMETERS: nothing
354 # RETURNS: nothing
355 # DESCRIPTION: validates commandline parameter
356 #===============================================================================
357 sub check_cmdline_param () {
358 my $err_config;
359 my $err_counter = 0;
360 if(not defined($cfg_file)) {
361 $cfg_file = "/etc/gosa-si/server.conf";
362 if(! -r $cfg_file) {
363 $err_config = "please specify a config file";
364 $err_counter += 1;
365 }
366 }
367 if( $err_counter > 0 ) {
368 &usage( "", 1 );
369 if( defined( $err_config)) { print STDERR "$err_config\n"}
370 print STDERR "\n";
371 exit( -1 );
372 }
373 }
376 #=== FUNCTION ================================================================
377 # NAME: check_pid
378 # PARAMETERS: nothing
379 # RETURNS: nothing
380 # DESCRIPTION: handels pid processing
381 #===============================================================================
382 sub check_pid {
383 $pid = -1;
384 # Check, if we are already running
385 if( open(LOCK_FILE, "<$pid_file") ) {
386 $pid = <LOCK_FILE>;
387 if( defined $pid ) {
388 chomp( $pid );
389 if( -f "/proc/$pid/stat" ) {
390 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
391 if( $stat ) {
392 daemon_log("ERROR: Already running",1);
393 close( LOCK_FILE );
394 exit -1;
395 }
396 }
397 }
398 close( LOCK_FILE );
399 unlink( $pid_file );
400 }
402 # create a syslog msg if it is not to possible to open PID file
403 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
404 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
405 if (open(LOCK_FILE, '<', $pid_file)
406 && ($pid = <LOCK_FILE>))
407 {
408 chomp($pid);
409 $msg .= "(PID $pid)\n";
410 } else {
411 $msg .= "(unable to read PID)\n";
412 }
413 if( ! ($foreground) ) {
414 openlog( $0, "cons,pid", "daemon" );
415 syslog( "warning", $msg );
416 closelog();
417 }
418 else {
419 print( STDERR " $msg " );
420 }
421 exit( -1 );
422 }
423 }
425 #=== FUNCTION ================================================================
426 # NAME: import_modules
427 # PARAMETERS: module_path - string - abs. path to the directory the modules
428 # are stored
429 # RETURNS: nothing
430 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
431 # state is on is imported by "require 'file';"
432 #===============================================================================
433 sub import_modules {
434 daemon_log(" ", 1);
436 if (not -e $modules_path) {
437 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
438 }
440 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
441 while (defined (my $file = readdir (DIR))) {
442 if (not $file =~ /(\S*?).pm$/) {
443 next;
444 }
445 my $mod_name = $1;
447 # ArpHandler switch
448 if( $file =~ /ArpHandler.pm/ ) {
449 if( $arp_enabled eq "false" ) { next; }
450 }
452 eval { require $file; };
453 if ($@) {
454 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
455 daemon_log("$@", 5);
456 } else {
457 my $info = eval($mod_name.'::get_module_info()');
458 # Only load module if get_module_info() returns a non-null object
459 if( $info ) {
460 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
461 $known_modules->{$mod_name} = $info;
462 daemon_log("0 INFO: module $mod_name loaded", 5);
463 }
464 }
465 }
466 close (DIR);
467 }
469 #=== FUNCTION ================================================================
470 # NAME: password_check
471 # PARAMETERS: nothing
472 # RETURNS: nothing
473 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
474 # the same password
475 #===============================================================================
476 sub password_check {
477 my $passwd_hash = {};
478 while (my ($mod_name, $mod_info) = each %$known_modules) {
479 my $mod_passwd = @$mod_info[1];
480 if (not defined $mod_passwd) { next; }
481 if (not exists $passwd_hash->{$mod_passwd}) {
482 $passwd_hash->{$mod_passwd} = $mod_name;
484 # escalates critical error
485 } else {
486 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
487 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
488 exit( -1 );
489 }
490 }
492 }
495 #=== FUNCTION ================================================================
496 # NAME: sig_int_handler
497 # PARAMETERS: signal - string - signal arose from system
498 # RETURNS: nothing
499 # DESCRIPTION: handels tasks to be done befor signal becomes active
500 #===============================================================================
501 sub sig_int_handler {
502 my ($signal) = @_;
504 # if (defined($ldap_handle)) {
505 # $ldap_handle->disconnect;
506 # }
507 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
510 daemon_log("shutting down gosa-si-server", 1);
511 system("kill `ps -C gosa-si-server -o pid=`");
512 }
513 $SIG{INT} = \&sig_int_handler;
516 sub check_key_and_xml_validity {
517 my ($crypted_msg, $module_key, $session_id) = @_;
518 my $msg;
519 my $msg_hash;
520 my $error_string;
521 eval{
522 $msg = &decrypt_msg($crypted_msg, $module_key);
524 if ($msg =~ /<xml>/i){
525 $msg =~ s/\s+/ /g; # just for better daemon_log
526 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
527 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
529 ##############
530 # check header
531 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
532 my $header_l = $msg_hash->{'header'};
533 if( 1 > @{$header_l} ) { die 'empty header tag'; }
534 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
535 my $header = @{$header_l}[0];
536 if( 0 == length $header) { die 'empty string in header tag'; }
538 ##############
539 # check source
540 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
541 my $source_l = $msg_hash->{'source'};
542 if( 1 > @{$source_l} ) { die 'empty source tag'; }
543 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
544 my $source = @{$source_l}[0];
545 if( 0 == length $source) { die 'source error'; }
547 ##############
548 # check target
549 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
550 my $target_l = $msg_hash->{'target'};
551 if( 1 > @{$target_l} ) { die 'empty target tag'; }
552 }
553 };
554 if($@) {
555 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
556 $msg = undef;
557 $msg_hash = undef;
558 }
560 return ($msg, $msg_hash);
561 }
564 sub check_outgoing_xml_validity {
565 my ($msg, $session_id) = @_;
567 my $msg_hash;
568 eval{
569 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
571 ##############
572 # check header
573 my $header_l = $msg_hash->{'header'};
574 if( 1 != @{$header_l} ) {
575 die 'no or more than one headers specified';
576 }
577 my $header = @{$header_l}[0];
578 if( 0 == length $header) {
579 die 'header has length 0';
580 }
582 ##############
583 # check source
584 my $source_l = $msg_hash->{'source'};
585 if( 1 != @{$source_l} ) {
586 die 'no or more than 1 sources specified';
587 }
588 my $source = @{$source_l}[0];
589 if( 0 == length $source) {
590 die 'source has length 0';
591 }
592 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
593 $source =~ /^GOSA$/i ) {
594 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
595 }
597 ##############
598 # check target
599 my $target_l = $msg_hash->{'target'};
600 if( 0 == @{$target_l} ) {
601 die "no targets specified";
602 }
603 foreach my $target (@$target_l) {
604 if( 0 == length $target) {
605 die "target has length 0";
606 }
607 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
608 $target =~ /^GOSA$/i ||
609 $target =~ /^\*$/ ||
610 $target =~ /KNOWN_SERVER/i ||
611 $target =~ /JOBDB/i ||
612 $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 ){
613 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
614 }
615 }
616 };
617 if($@) {
618 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
619 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
620 $msg_hash = undef;
621 }
623 return ($msg_hash);
624 }
627 sub input_from_known_server {
628 my ($input, $remote_ip, $session_id) = @_ ;
629 my ($msg, $msg_hash, $module);
631 my $sql_statement= "SELECT * FROM known_server";
632 my $query_res = $known_server_db->select_dbentry( $sql_statement );
634 while( my ($hit_num, $hit) = each %{ $query_res } ) {
635 my $host_name = $hit->{hostname};
636 if( not $host_name =~ "^$remote_ip") {
637 next;
638 }
639 my $host_key = $hit->{hostkey};
640 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
641 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
643 # check if module can open msg envelope with module key
644 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
645 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
646 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
647 daemon_log("$@", 8);
648 next;
649 }
650 else {
651 $msg = $tmp_msg;
652 $msg_hash = $tmp_msg_hash;
653 $module = "ServerPackages";
654 last;
655 }
656 }
658 if( (!$msg) || (!$msg_hash) || (!$module) ) {
659 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
660 }
662 return ($msg, $msg_hash, $module);
663 }
666 sub input_from_known_client {
667 my ($input, $remote_ip, $session_id) = @_ ;
668 my ($msg, $msg_hash, $module);
670 my $sql_statement= "SELECT * FROM known_clients";
671 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
672 while( my ($hit_num, $hit) = each %{ $query_res } ) {
673 my $host_name = $hit->{hostname};
674 if( not $host_name =~ /^$remote_ip:\d*$/) {
675 next;
676 }
677 my $host_key = $hit->{hostkey};
678 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
679 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
681 # check if module can open msg envelope with module key
682 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
684 if( (!$msg) || (!$msg_hash) ) {
685 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
686 &daemon_log("$@", 8);
687 next;
688 }
689 else {
690 $module = "ClientPackages";
691 last;
692 }
693 }
695 if( (!$msg) || (!$msg_hash) || (!$module) ) {
696 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
697 }
699 return ($msg, $msg_hash, $module);
700 }
703 sub input_from_unknown_host {
704 no strict "refs";
705 my ($input, $session_id) = @_ ;
706 my ($msg, $msg_hash, $module);
707 my $error_string;
709 my %act_modules = %$known_modules;
711 while( my ($mod, $info) = each(%act_modules)) {
713 # check a key exists for this module
714 my $module_key = ${$mod."_key"};
715 if( not defined $module_key ) {
716 if( $mod eq 'ArpHandler' ) {
717 next;
718 }
719 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
720 next;
721 }
722 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
724 # check if module can open msg envelope with module key
725 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
726 if( (not defined $msg) || (not defined $msg_hash) ) {
727 next;
728 }
729 else {
730 $module = $mod;
731 last;
732 }
733 }
735 if( (!$msg) || (!$msg_hash) || (!$module)) {
736 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
737 }
739 return ($msg, $msg_hash, $module);
740 }
743 sub create_ciphering {
744 my ($passwd) = @_;
745 if((!defined($passwd)) || length($passwd)==0) {
746 $passwd = "";
747 }
748 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
749 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
750 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
751 $my_cipher->set_iv($iv);
752 return $my_cipher;
753 }
756 sub encrypt_msg {
757 my ($msg, $key) = @_;
758 my $my_cipher = &create_ciphering($key);
759 my $len;
760 {
761 use bytes;
762 $len= 16-length($msg)%16;
763 }
764 $msg = "\0"x($len).$msg;
765 $msg = $my_cipher->encrypt($msg);
766 chomp($msg = &encode_base64($msg));
767 # there are no newlines allowed inside msg
768 $msg=~ s/\n//g;
769 return $msg;
770 }
773 sub decrypt_msg {
775 my ($msg, $key) = @_ ;
776 $msg = &decode_base64($msg);
777 my $my_cipher = &create_ciphering($key);
778 $msg = $my_cipher->decrypt($msg);
779 $msg =~ s/\0*//g;
780 return $msg;
781 }
784 sub get_encrypt_key {
785 my ($target) = @_ ;
786 my $encrypt_key;
787 my $error = 0;
789 # target can be in known_server
790 if( not defined $encrypt_key ) {
791 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
792 my $query_res = $known_server_db->select_dbentry( $sql_statement );
793 while( my ($hit_num, $hit) = each %{ $query_res } ) {
794 my $host_name = $hit->{hostname};
795 if( $host_name ne $target ) {
796 next;
797 }
798 $encrypt_key = $hit->{hostkey};
799 last;
800 }
801 }
803 # target can be in known_client
804 if( not defined $encrypt_key ) {
805 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
806 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
807 while( my ($hit_num, $hit) = each %{ $query_res } ) {
808 my $host_name = $hit->{hostname};
809 if( $host_name ne $target ) {
810 next;
811 }
812 $encrypt_key = $hit->{hostkey};
813 last;
814 }
815 }
817 return $encrypt_key;
818 }
821 #=== FUNCTION ================================================================
822 # NAME: open_socket
823 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
824 # [PeerPort] string necessary if port not appended by PeerAddr
825 # RETURNS: socket IO::Socket::INET
826 # DESCRIPTION: open a socket to PeerAddr
827 #===============================================================================
828 sub open_socket {
829 my ($PeerAddr, $PeerPort) = @_ ;
830 if(defined($PeerPort)){
831 $PeerAddr = $PeerAddr.":".$PeerPort;
832 }
833 my $socket;
834 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
835 Porto => "tcp",
836 Type => SOCK_STREAM,
837 Timeout => 5,
838 );
839 if(not defined $socket) {
840 return;
841 }
842 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
843 return $socket;
844 }
847 sub get_local_ip_for_remote_ip {
848 my $remote_ip= shift;
849 my $result="0.0.0.0";
851 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
852 if($remote_ip eq "127.0.0.1") {
853 $result = "127.0.0.1";
854 } else {
855 my $PROC_NET_ROUTE= ('/proc/net/route');
857 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
858 or die "Could not open $PROC_NET_ROUTE";
860 my @ifs = <PROC_NET_ROUTE>;
862 close(PROC_NET_ROUTE);
864 # Eat header line
865 shift @ifs;
866 chomp @ifs;
867 foreach my $line(@ifs) {
868 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
869 my $destination;
870 my $mask;
871 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
872 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
873 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
874 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
875 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
876 # destination matches route, save mac and exit
877 $result= &get_ip($Iface);
878 last;
879 }
880 }
881 }
882 } else {
883 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
884 }
885 return $result;
886 }
889 sub send_msg_to_target {
890 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
891 my $error = 0;
892 my $header;
893 my $timestamp = &get_time();
894 my $new_status;
895 my $act_status;
896 my ($sql_statement, $res);
898 if( $msg_header ) {
899 $header = "'$msg_header'-";
900 } else {
901 $header = "";
902 }
904 # Patch the source ip
905 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
906 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
907 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
908 }
910 # encrypt xml msg
911 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
913 # opensocket
914 my $socket = &open_socket($address);
915 if( !$socket ) {
916 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
917 $error++;
918 }
920 if( $error == 0 ) {
921 # send xml msg
922 print $socket $crypted_msg."\n";
924 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
925 daemon_log("$session_id DEBUG: message:\n$msg", 9);
927 }
929 # close socket in any case
930 if( $socket ) {
931 close $socket;
932 }
934 if( $error > 0 ) { $new_status = "down"; }
935 else { $new_status = $msg_header; }
938 # known_clients
939 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
940 $res = $known_clients_db->select_dbentry($sql_statement);
941 if( keys(%$res) == 1) {
942 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
943 if ($act_status eq "down" && $new_status eq "down") {
944 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
945 $res = $known_clients_db->del_dbentry($sql_statement);
946 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
947 } else {
948 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
949 $res = $known_clients_db->update_dbentry($sql_statement);
950 if($new_status eq "down"){
951 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
952 } else {
953 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
954 }
955 }
956 }
958 # known_server
959 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
960 $res = $known_server_db->select_dbentry($sql_statement);
961 if( keys(%$res) == 1) {
962 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
963 if ($act_status eq "down" && $new_status eq "down") {
964 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
965 $res = $known_server_db->del_dbentry($sql_statement);
966 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
967 }
968 else {
969 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
970 $res = $known_server_db->update_dbentry($sql_statement);
971 if($new_status eq "down"){
972 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
973 } else {
974 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
975 }
976 }
977 }
978 return $error;
979 }
982 sub update_jobdb_status_for_send_msgs {
983 my ($answer, $error) = @_;
984 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
985 my $jobdb_id = $1;
987 # sending msg faild
988 if( $error ) {
989 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
990 my $sql_statement = "UPDATE $job_queue_tn ".
991 "SET status='error', result='can not deliver msg, please consult log file' ".
992 "WHERE id=$jobdb_id";
993 my $res = $job_db->update_dbentry($sql_statement);
994 }
996 # sending msg was successful
997 } else {
998 my $sql_statement = "UPDATE $job_queue_tn ".
999 "SET status='done' ".
1000 "WHERE id=$jobdb_id AND status='processed'";
1001 my $res = $job_db->update_dbentry($sql_statement);
1002 }
1003 }
1004 }
1007 sub sig_handler {
1008 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1009 daemon_log("0 INFO got signal '$signal'", 1);
1010 $kernel->sig_handled();
1011 return;
1012 }
1015 sub msg_to_decrypt {
1016 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1017 my $session_id = $session->ID;
1018 my ($msg, $msg_hash, $module);
1019 my $error = 0;
1021 # hole neue msg aus @msgs_to_decrypt
1022 my $next_msg = shift @msgs_to_decrypt;
1024 # entschlüssle sie
1026 # msg is from a new client or gosa
1027 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1028 # msg is from a gosa-si-server
1029 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1030 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1031 }
1032 # msg is from a gosa-si-client
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1035 }
1036 # an error occurred
1037 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1039 # could not understand a msg from its server the client cause a re-registering process
1040 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1041 "' to cause a re-registering of the client if necessary", 3);
1042 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1043 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1044 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1045 my $host_name = $hit->{'hostname'};
1046 my $host_key = $hit->{'hostkey'};
1047 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1048 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1049 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1050 }
1051 $error++;
1052 }
1055 my $header;
1056 my $target;
1057 my $source;
1058 my $done = 0;
1059 my $sql;
1060 my $res;
1062 # check whether this message should be processed here
1063 if ($error == 0) {
1064 $header = @{$msg_hash->{'header'}}[0];
1065 $target = @{$msg_hash->{'target'}}[0];
1066 $source = @{$msg_hash->{'source'}}[0];
1067 my $not_found_in_known_clients_db = 0;
1068 my $not_found_in_known_server_db = 0;
1069 my $not_found_in_foreign_clients_db = 0;
1070 my $local_address;
1071 my ($target_ip, $target_port) = split(':', $target);
1072 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1073 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1074 } else {
1075 $local_address = $server_address;
1076 }
1078 # target and source is equal to GOSA -> process here
1079 if (not $done) {
1080 if ($target eq "GOSA" && $source eq "GOSA") {
1081 $done = 1;
1082 }
1083 }
1085 # target is own address without forward_to_gosa-tag -> process here
1086 if (not $done) {
1087 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1088 $done = 1;
1089 if ($source eq "GOSA") {
1090 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1091 }
1092 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1093 }
1094 }
1096 # target is a client address in known_clients -> process here
1097 if (not $done) {
1098 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1099 $res = $known_clients_db->select_dbentry($sql);
1100 if (keys(%$res) > 0) {
1101 $done = 1;
1102 my $hostname = $res->{1}->{'hostname'};
1103 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1104 #print STDERR "target is a client address in known_clients -> process here\n";
1105 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1106 if ($source eq "GOSA") {
1107 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1108 }
1110 } else {
1111 $not_found_in_known_clients_db = 1;
1112 }
1113 }
1115 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1116 if (not $done) {
1117 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1118 my $gosa_at;
1119 my $gosa_session_id;
1120 if (($target eq $local_address) && (defined $forward_to_gosa)){
1121 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1122 if ($gosa_at ne $local_address) {
1123 $done = 1;
1124 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1125 }
1126 }
1127 }
1129 # if message should be processed here -> add message to incoming_db
1130 if ($done) {
1131 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1132 # so gosa-si-server knows how to process this kind of messages
1133 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1134 $module = "GosaPackages";
1135 }
1137 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1138 primkey=>[],
1139 headertag=>$header,
1140 targettag=>$target,
1141 xmlmessage=>&encode_base64($msg),
1142 timestamp=>&get_time,
1143 module=>$module,
1144 sessionid=>$session_id,
1145 } );
1146 }
1148 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1149 if (not $done) {
1150 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1151 my $gosa_at;
1152 my $gosa_session_id;
1153 if (($target eq $local_address) && (defined $forward_to_gosa)){
1154 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1155 if ($gosa_at eq $local_address) {
1156 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1157 if( defined $session_reference ) {
1158 $heap = $session_reference->get_heap();
1159 }
1160 if(exists $heap->{'client'}) {
1161 $msg = &encrypt_msg($msg, $GosaPackages_key);
1162 $heap->{'client'}->put($msg);
1163 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1164 }
1165 $done = 1;
1166 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1167 }
1168 }
1170 }
1172 # target is a client address in foreign_clients -> forward to registration server
1173 if (not $done) {
1174 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1175 $res = $foreign_clients_db->select_dbentry($sql);
1176 if (keys(%$res) > 0) {
1177 my $hostname = $res->{1}->{'hostname'};
1178 my ($host_ip, $host_port) = split(/:/, $hostname);
1179 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1180 my $regserver = $res->{1}->{'regserver'};
1181 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1182 my $res = $known_server_db->select_dbentry($sql);
1183 if (keys(%$res) > 0) {
1184 my $regserver_key = $res->{1}->{'hostkey'};
1185 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1186 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1187 if ($source eq "GOSA") {
1188 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1189 }
1190 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1191 }
1192 $done = 1;
1193 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1194 } else {
1195 $not_found_in_foreign_clients_db = 1;
1196 }
1197 }
1199 # target is a server address -> forward to server
1200 if (not $done) {
1201 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1202 $res = $known_server_db->select_dbentry($sql);
1203 if (keys(%$res) > 0) {
1204 my $hostkey = $res->{1}->{'hostkey'};
1206 if ($source eq "GOSA") {
1207 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1208 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1210 }
1212 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1213 $done = 1;
1214 #print STDERR "target is a server address -> forward to server\n";
1215 } else {
1216 $not_found_in_known_server_db = 1;
1217 }
1218 }
1221 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1222 if ( $not_found_in_foreign_clients_db
1223 && $not_found_in_known_server_db
1224 && $not_found_in_known_clients_db) {
1225 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1226 primkey=>[],
1227 headertag=>$header,
1228 targettag=>$target,
1229 xmlmessage=>&encode_base64($msg),
1230 timestamp=>&get_time,
1231 module=>$module,
1232 sessionid=>$session_id,
1233 } );
1234 $done = 1;
1235 }
1238 if (not $done) {
1239 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1240 if ($source eq "GOSA") {
1241 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1242 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1244 my $session_reference = $kernel->ID_id_to_session($session_id);
1245 if( defined $session_reference ) {
1246 $heap = $session_reference->get_heap();
1247 }
1248 if(exists $heap->{'client'}) {
1249 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1250 $heap->{'client'}->put($error_msg);
1251 }
1252 }
1253 }
1255 }
1257 return;
1258 }
1261 sub next_task {
1262 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1263 my $running_task = POE::Wheel::Run->new(
1264 Program => sub { process_task($session, $heap, $task) },
1265 StdioFilter => POE::Filter::Reference->new(),
1266 StdoutEvent => "task_result",
1267 StderrEvent => "task_debug",
1268 CloseEvent => "task_done",
1269 );
1270 $heap->{task}->{ $running_task->ID } = $running_task;
1271 }
1273 sub handle_task_result {
1274 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1275 my $client_answer = $result->{'answer'};
1276 if( $client_answer =~ s/session_id=(\d+)$// ) {
1277 my $session_id = $1;
1278 if( defined $session_id ) {
1279 my $session_reference = $kernel->ID_id_to_session($session_id);
1280 if( defined $session_reference ) {
1281 $heap = $session_reference->get_heap();
1282 }
1283 }
1285 if(exists $heap->{'client'}) {
1286 $heap->{'client'}->put($client_answer);
1287 }
1288 }
1289 $kernel->sig(CHLD => "child_reap");
1290 }
1292 sub handle_task_debug {
1293 my $result = $_[ARG0];
1294 print STDERR "$result\n";
1295 }
1297 sub handle_task_done {
1298 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1299 delete $heap->{task}->{$task_id};
1300 }
1302 sub process_task {
1303 no strict "refs";
1304 #CHECK: Not @_[...]?
1305 my ($session, $heap, $task) = @_;
1306 my $error = 0;
1307 my $answer_l;
1308 my ($answer_header, @answer_target_l, $answer_source);
1309 my $client_answer = "";
1311 # prepare all variables needed to process message
1312 #my $msg = $task->{'xmlmessage'};
1313 my $msg = &decode_base64($task->{'xmlmessage'});
1314 my $incoming_id = $task->{'id'};
1315 my $module = $task->{'module'};
1316 my $header = $task->{'headertag'};
1317 my $session_id = $task->{'sessionid'};
1318 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1319 my $source = @{$msg_hash->{'source'}}[0];
1321 # set timestamp of incoming client uptodate, so client will not
1322 # be deleted from known_clients because of expiration
1323 my $act_time = &get_time();
1324 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1325 my $res = $known_clients_db->exec_statement($sql);
1327 ######################
1328 # process incoming msg
1329 if( $error == 0) {
1330 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1331 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1332 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1334 if ( 0 < @{$answer_l} ) {
1335 my $answer_str = join("\n", @{$answer_l});
1336 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1337 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1338 }
1339 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1340 } else {
1341 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1342 }
1344 }
1345 if( !$answer_l ) { $error++ };
1347 ########
1348 # answer
1349 if( $error == 0 ) {
1351 foreach my $answer ( @{$answer_l} ) {
1352 # check outgoing msg to xml validity
1353 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1354 if( not defined $answer_hash ) { next; }
1356 $answer_header = @{$answer_hash->{'header'}}[0];
1357 @answer_target_l = @{$answer_hash->{'target'}};
1358 $answer_source = @{$answer_hash->{'source'}}[0];
1360 # deliver msg to all targets
1361 foreach my $answer_target ( @answer_target_l ) {
1363 # targets of msg are all gosa-si-clients in known_clients_db
1364 if( $answer_target eq "*" ) {
1365 # answer is for all clients
1366 my $sql_statement= "SELECT * FROM known_clients";
1367 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1368 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1369 my $host_name = $hit->{hostname};
1370 my $host_key = $hit->{hostkey};
1371 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1372 &update_jobdb_status_for_send_msgs($answer, $error);
1373 }
1374 }
1376 # targets of msg are all gosa-si-server in known_server_db
1377 elsif( $answer_target eq "KNOWN_SERVER" ) {
1378 # answer is for all server in known_server
1379 my $sql_statement= "SELECT * FROM $known_server_tn";
1380 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1381 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1382 my $host_name = $hit->{hostname};
1383 my $host_key = $hit->{hostkey};
1384 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1385 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1386 &update_jobdb_status_for_send_msgs($answer, $error);
1387 }
1388 }
1390 # target of msg is GOsa
1391 elsif( $answer_target eq "GOSA" ) {
1392 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1393 my $add_on = "";
1394 if( defined $session_id ) {
1395 $add_on = ".session_id=$session_id";
1396 }
1397 # answer is for GOSA and has to returned to connected client
1398 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1399 $client_answer = $gosa_answer.$add_on;
1400 }
1402 # target of msg is job queue at this host
1403 elsif( $answer_target eq "JOBDB") {
1404 $answer =~ /<header>(\S+)<\/header>/;
1405 my $header;
1406 if( defined $1 ) { $header = $1; }
1407 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1408 &update_jobdb_status_for_send_msgs($answer, $error);
1409 }
1411 # target of msg is a mac address
1412 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 ) {
1413 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1414 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1415 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1416 my $found_ip_flag = 0;
1417 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1418 my $host_name = $hit->{hostname};
1419 my $host_key = $hit->{hostkey};
1420 $answer =~ s/$answer_target/$host_name/g;
1421 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1422 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1423 &update_jobdb_status_for_send_msgs($answer, $error);
1424 $found_ip_flag++ ;
1425 }
1426 if( $found_ip_flag == 0) {
1427 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1428 }
1430 # answer is for one specific host
1431 } else {
1432 # get encrypt_key
1433 my $encrypt_key = &get_encrypt_key($answer_target);
1434 if( not defined $encrypt_key ) {
1435 # unknown target
1436 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1437 next;
1438 }
1439 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1440 &update_jobdb_status_for_send_msgs($answer, $error);
1441 }
1442 }
1443 }
1444 }
1446 my $filter = POE::Filter::Reference->new();
1447 my %result = (
1448 status => "seems ok to me",
1449 answer => $client_answer,
1450 );
1452 my $output = $filter->put( [ \%result ] );
1453 print @$output;
1456 }
1458 sub session_start {
1459 my ($kernel) = $_[KERNEL];
1460 $global_kernel = $kernel;
1461 $kernel->yield('register_at_foreign_servers');
1462 $kernel->yield('create_fai_server_db', $fai_server_tn );
1463 $kernel->yield('create_fai_release_db', $fai_release_tn );
1464 $kernel->yield('watch_for_next_tasks');
1465 $kernel->sig(USR1 => "sig_handler");
1466 $kernel->sig(USR2 => "recreate_packages_db");
1467 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1468 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1469 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1470 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1471 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1472 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1473 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1476 }
1479 sub watch_for_done_jobs {
1480 #CHECK: $heap for what?
1481 my ($kernel,$heap) = @_[KERNEL, HEAP];
1483 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1484 my $res = $job_db->select_dbentry( $sql_statement );
1486 while( my ($id, $hit) = each %{$res} ) {
1487 my $jobdb_id = $hit->{id};
1488 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1489 my $res = $job_db->del_dbentry($sql_statement);
1490 }
1492 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1493 }
1496 # if a job got an update or was modified anyway, send to all other si-server an update message
1497 # of this jobs
1498 sub watch_for_modified_jobs {
1499 my ($kernel,$heap) = @_[KERNEL, HEAP];
1501 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1502 my $res = $job_db->select_dbentry( $sql_statement );
1504 # if db contains no jobs which should be update, do nothing
1505 if (keys %$res != 0) {
1507 if ($job_synchronization eq "true") {
1508 # make out of the db result a gosa-si message
1509 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1511 # update all other SI-server
1512 &inform_all_other_si_server($update_msg);
1513 }
1515 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1516 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1517 $res = $job_db->update_dbentry($sql_statement);
1518 }
1520 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1521 }
1524 sub watch_for_new_jobs {
1525 if($watch_for_new_jobs_in_progress == 0) {
1526 $watch_for_new_jobs_in_progress = 1;
1527 my ($kernel,$heap) = @_[KERNEL, HEAP];
1529 # check gosa job quaeue for jobs with executable timestamp
1530 my $timestamp = &get_time();
1531 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1532 my $res = $job_db->exec_statement( $sql_statement );
1534 # Merge all new jobs that would do the same actions
1535 my @drops;
1536 my $hits;
1537 foreach my $hit (reverse @{$res} ) {
1538 my $macaddress= lc @{$hit}[8];
1539 my $headertag= @{$hit}[5];
1540 if(
1541 defined($hits->{$macaddress}) &&
1542 defined($hits->{$macaddress}->{$headertag}) &&
1543 defined($hits->{$macaddress}->{$headertag}[0])
1544 ) {
1545 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1546 }
1547 $hits->{$macaddress}->{$headertag}= $hit;
1548 }
1550 # Delete new jobs with a matching job in state 'processing'
1551 foreach my $macaddress (keys %{$hits}) {
1552 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1553 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1554 if(defined($jobdb_id)) {
1555 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1556 my $res = $job_db->exec_statement( $sql_statement );
1557 foreach my $hit (@{$res}) {
1558 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1559 }
1560 } else {
1561 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1562 }
1563 }
1564 }
1566 # Commit deletion
1567 $job_db->exec_statementlist(\@drops);
1569 # Look for new jobs that could be executed
1570 foreach my $macaddress (keys %{$hits}) {
1572 # Look if there is an executing job
1573 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1574 my $res = $job_db->exec_statement( $sql_statement );
1576 # Skip new jobs for host if there is a processing job
1577 if(defined($res) and defined @{$res}[0]) {
1578 next;
1579 }
1581 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1582 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1583 if(defined($jobdb_id)) {
1584 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1586 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1587 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1588 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1590 # expect macaddress is unique!!!!!!
1591 my $target = $res_hash->{1}->{hostname};
1593 # change header
1594 $job_msg =~ s/<header>job_/<header>gosa_/;
1596 # add sqlite_id
1597 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1599 $job_msg =~ /<header>(\S+)<\/header>/;
1600 my $header = $1 ;
1601 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1603 # update status in job queue to 'processing'
1604 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1605 my $res = $job_db->update_dbentry($sql_statement);
1606 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1608 # We don't want parallel processing
1609 last;
1610 }
1611 }
1612 }
1614 $watch_for_new_jobs_in_progress = 0;
1615 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1616 }
1617 }
1621 sub watch_for_new_messages {
1622 my ($kernel,$heap) = @_[KERNEL, HEAP];
1623 my @coll_user_msg; # collection list of outgoing messages
1625 # check messaging_db for new incoming messages with executable timestamp
1626 my $timestamp = &get_time();
1627 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1628 my $res = $messaging_db->exec_statement( $sql_statement );
1629 foreach my $hit (@{$res}) {
1631 # create outgoing messages
1632 my $message_to = @{$hit}[3];
1633 # translate message_to to plain login name
1634 my @message_to_l = split(/,/, $message_to);
1635 my %receiver_h;
1636 foreach my $receiver (@message_to_l) {
1637 if ($receiver =~ /^u_([\s\S]*)$/) {
1638 $receiver_h{$1} = 0;
1639 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1640 my $group_name = $1;
1641 # fetch all group members from ldap and add them to receiver hash
1642 my $ldap_handle = &get_ldap_handle();
1643 if (defined $ldap_handle) {
1644 my $mesg = $ldap_handle->search(
1645 base => $ldap_base,
1646 scope => 'sub',
1647 attrs => ['memberUid'],
1648 filter => "cn=$group_name",
1649 );
1650 if ($mesg->count) {
1651 my @entries = $mesg->entries;
1652 foreach my $entry (@entries) {
1653 my @receivers= $entry->get_value("memberUid");
1654 foreach my $receiver (@receivers) {
1655 $receiver_h{$1} = 0;
1656 }
1657 }
1658 }
1659 # translating errors ?
1660 if ($mesg->code) {
1661 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1662 }
1663 # ldap handle error ?
1664 } else {
1665 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1666 }
1667 } else {
1668 my $sbjct = &encode_base64(@{$hit}[1]);
1669 my $msg = &encode_base64(@{$hit}[7]);
1670 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1671 }
1672 }
1673 my @receiver_l = keys(%receiver_h);
1675 my $message_id = @{$hit}[0];
1677 #add each outgoing msg to messaging_db
1678 my $receiver;
1679 foreach $receiver (@receiver_l) {
1680 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1681 "VALUES ('".
1682 $message_id."', '". # id
1683 @{$hit}[1]."', '". # subject
1684 @{$hit}[2]."', '". # message_from
1685 $receiver."', '". # message_to
1686 "none"."', '". # flag
1687 "out"."', '". # direction
1688 @{$hit}[6]."', '". # delivery_time
1689 @{$hit}[7]."', '". # message
1690 $timestamp."'". # timestamp
1691 ")";
1692 &daemon_log("M DEBUG: $sql_statement", 1);
1693 my $res = $messaging_db->exec_statement($sql_statement);
1694 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1695 }
1697 # set incoming message to flag d=deliverd
1698 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1699 &daemon_log("M DEBUG: $sql_statement", 7);
1700 $res = $messaging_db->update_dbentry($sql_statement);
1701 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1702 }
1704 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1705 return;
1706 }
1708 sub watch_for_delivery_messages {
1709 my ($kernel, $heap) = @_[KERNEL, HEAP];
1711 # select outgoing messages
1712 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1713 #&daemon_log("0 DEBUG: $sql", 7);
1714 my $res = $messaging_db->exec_statement( $sql_statement );
1716 # build out msg for each usr
1717 foreach my $hit (@{$res}) {
1718 my $receiver = @{$hit}[3];
1719 my $msg_id = @{$hit}[0];
1720 my $subject = @{$hit}[1];
1721 my $message = @{$hit}[7];
1723 # resolve usr -> host where usr is logged in
1724 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1725 #&daemon_log("0 DEBUG: $sql", 7);
1726 my $res = $login_users_db->exec_statement($sql);
1728 # reciver is logged in nowhere
1729 if (not ref(@$res[0]) eq "ARRAY") { next; }
1731 my $send_succeed = 0;
1732 foreach my $hit (@$res) {
1733 my $receiver_host = @$hit[0];
1734 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1736 # fetch key to encrypt msg propperly for usr/host
1737 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1738 &daemon_log("0 DEBUG: $sql", 7);
1739 my $res = $known_clients_db->exec_statement($sql);
1741 # host is already down
1742 if (not ref(@$res[0]) eq "ARRAY") { next; }
1744 # host is on
1745 my $receiver_key = @{@{$res}[0]}[2];
1746 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1747 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1748 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1749 if ($error == 0 ) {
1750 $send_succeed++ ;
1751 }
1752 }
1754 if ($send_succeed) {
1755 # set outgoing msg at db to deliverd
1756 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1757 &daemon_log("0 DEBUG: $sql", 7);
1758 my $res = $messaging_db->exec_statement($sql);
1759 }
1760 }
1762 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1763 return;
1764 }
1767 sub watch_for_done_messages {
1768 my ($kernel,$heap) = @_[KERNEL, HEAP];
1770 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1771 #&daemon_log("0 DEBUG: $sql", 7);
1772 my $res = $messaging_db->exec_statement($sql);
1774 foreach my $hit (@{$res}) {
1775 my $msg_id = @{$hit}[0];
1777 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1778 #&daemon_log("0 DEBUG: $sql", 7);
1779 my $res = $messaging_db->exec_statement($sql);
1781 # not all usr msgs have been seen till now
1782 if ( ref(@$res[0]) eq "ARRAY") { next; }
1784 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1785 #&daemon_log("0 DEBUG: $sql", 7);
1786 $res = $messaging_db->exec_statement($sql);
1788 }
1790 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1791 return;
1792 }
1795 sub watch_for_old_known_clients {
1796 my ($kernel,$heap) = @_[KERNEL, HEAP];
1798 my $sql_statement = "SELECT * FROM $known_clients_tn";
1799 my $res = $known_clients_db->select_dbentry( $sql_statement );
1801 my $act_time = int(&get_time());
1803 while ( my ($hit_num, $hit) = each %$res) {
1804 my $expired_timestamp = int($hit->{'timestamp'});
1805 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1806 my $dt = DateTime->new( year => $1,
1807 month => $2,
1808 day => $3,
1809 hour => $4,
1810 minute => $5,
1811 second => $6,
1812 );
1814 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1815 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1816 if ($act_time > $expired_timestamp) {
1817 my $hostname = $hit->{'hostname'};
1818 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1819 my $del_res = $known_clients_db->exec_statement($del_sql);
1821 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1822 }
1824 }
1826 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1827 }
1830 sub watch_for_next_tasks {
1831 my ($kernel,$heap) = @_[KERNEL, HEAP];
1833 my $sql = "SELECT * FROM $incoming_tn";
1834 my $res = $incoming_db->select_dbentry($sql);
1836 while ( my ($hit_num, $hit) = each %$res) {
1837 my $headertag = $hit->{'headertag'};
1838 if ($headertag =~ /^answer_(\d+)/) {
1839 # do not start processing, this message is for a still running POE::Wheel
1840 next;
1841 }
1842 my $message_id = $hit->{'id'};
1843 $kernel->yield('next_task', $hit);
1845 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1846 my $res = $incoming_db->exec_statement($sql);
1847 }
1849 $kernel->delay_set('watch_for_next_tasks', 0.1);
1850 }
1853 sub get_ldap_handle {
1854 my ($session_id) = @_;
1855 my $heap;
1856 my $ldap_handle;
1858 if (not defined $session_id ) { $session_id = 0 };
1859 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1861 if ($session_id == 0) {
1862 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1863 $ldap_handle = Net::LDAP->new( $ldap_uri );
1864 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1866 } else {
1867 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1868 if( defined $session_reference ) {
1869 $heap = $session_reference->get_heap();
1870 }
1872 if (not defined $heap) {
1873 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1874 return;
1875 }
1877 # TODO: This "if" is nonsense, because it doesn't prove that the
1878 # used handle is still valid - or if we've to reconnect...
1879 #if (not exists $heap->{ldap_handle}) {
1880 $ldap_handle = Net::LDAP->new( $ldap_uri );
1881 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1882 $heap->{ldap_handle} = $ldap_handle;
1883 #}
1884 }
1885 return $ldap_handle;
1886 }
1889 sub change_fai_state {
1890 my ($st, $targets, $session_id) = @_;
1891 $session_id = 0 if not defined $session_id;
1892 # Set FAI state to localboot
1893 my %mapActions= (
1894 reboot => '',
1895 update => 'softupdate',
1896 localboot => 'localboot',
1897 reinstall => 'install',
1898 rescan => '',
1899 wake => '',
1900 memcheck => 'memcheck',
1901 sysinfo => 'sysinfo',
1902 install => 'install',
1903 );
1905 # Return if this is unknown
1906 if (!exists $mapActions{ $st }){
1907 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1908 return;
1909 }
1911 my $state= $mapActions{ $st };
1913 my $ldap_handle = &get_ldap_handle($session_id);
1914 if( defined($ldap_handle) ) {
1916 # Build search filter for hosts
1917 my $search= "(&(objectClass=GOhard)";
1918 foreach (@{$targets}){
1919 $search.= "(macAddress=$_)";
1920 }
1921 $search.= ")";
1923 # If there's any host inside of the search string, procress them
1924 if (!($search =~ /macAddress/)){
1925 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1926 return;
1927 }
1929 # Perform search for Unit Tag
1930 my $mesg = $ldap_handle->search(
1931 base => $ldap_base,
1932 scope => 'sub',
1933 attrs => ['dn', 'FAIstate', 'objectClass'],
1934 filter => "$search"
1935 );
1937 if ($mesg->count) {
1938 my @entries = $mesg->entries;
1939 if (0 == @entries) {
1940 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1941 }
1943 foreach my $entry (@entries) {
1944 # Only modify entry if it is not set to '$state'
1945 if ($entry->get_value("FAIstate") ne "$state"){
1946 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1947 my $result;
1948 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1949 if (exists $tmp{'FAIobject'}){
1950 if ($state eq ''){
1951 $result= $ldap_handle->modify($entry->dn, changes => [
1952 delete => [ FAIstate => [] ] ]);
1953 } else {
1954 $result= $ldap_handle->modify($entry->dn, changes => [
1955 replace => [ FAIstate => $state ] ]);
1956 }
1957 } elsif ($state ne ''){
1958 $result= $ldap_handle->modify($entry->dn, changes => [
1959 add => [ objectClass => 'FAIobject' ],
1960 add => [ FAIstate => $state ] ]);
1961 }
1963 # Errors?
1964 if ($result->code){
1965 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1966 }
1967 } else {
1968 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1969 }
1970 }
1971 } else {
1972 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1973 }
1975 # if no ldap handle defined
1976 } else {
1977 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1978 }
1980 return;
1981 }
1984 sub change_goto_state {
1985 my ($st, $targets, $session_id) = @_;
1986 $session_id = 0 if not defined $session_id;
1988 # Switch on or off?
1989 my $state= $st eq 'active' ? 'active': 'locked';
1991 my $ldap_handle = &get_ldap_handle($session_id);
1992 if( defined($ldap_handle) ) {
1994 # Build search filter for hosts
1995 my $search= "(&(objectClass=GOhard)";
1996 foreach (@{$targets}){
1997 $search.= "(macAddress=$_)";
1998 }
1999 $search.= ")";
2001 # If there's any host inside of the search string, procress them
2002 if (!($search =~ /macAddress/)){
2003 return;
2004 }
2006 # Perform search for Unit Tag
2007 my $mesg = $ldap_handle->search(
2008 base => $ldap_base,
2009 scope => 'sub',
2010 attrs => ['dn', 'gotoMode'],
2011 filter => "$search"
2012 );
2014 if ($mesg->count) {
2015 my @entries = $mesg->entries;
2016 foreach my $entry (@entries) {
2018 # Only modify entry if it is not set to '$state'
2019 if ($entry->get_value("gotoMode") ne $state){
2021 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2022 my $result;
2023 $result= $ldap_handle->modify($entry->dn, changes => [
2024 replace => [ gotoMode => $state ] ]);
2026 # Errors?
2027 if ($result->code){
2028 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2029 }
2031 }
2032 }
2033 } else {
2034 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2035 }
2037 }
2038 }
2041 sub run_recreate_packages_db {
2042 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2043 my $session_id = $session->ID;
2044 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2045 $kernel->yield('create_fai_release_db', $fai_release_tn);
2046 $kernel->yield('create_fai_server_db', $fai_server_tn);
2047 return;
2048 }
2051 sub run_create_fai_server_db {
2052 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2053 my $session_id = $session->ID;
2054 my $task = POE::Wheel::Run->new(
2055 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2056 StdoutEvent => "session_run_result",
2057 StderrEvent => "session_run_debug",
2058 CloseEvent => "session_run_done",
2059 );
2061 $heap->{task}->{ $task->ID } = $task;
2062 return;
2063 }
2066 sub create_fai_server_db {
2067 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2068 my $result;
2070 if (not defined $session_id) { $session_id = 0; }
2071 my $ldap_handle = &get_ldap_handle();
2072 if(defined($ldap_handle)) {
2073 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2074 my $mesg= $ldap_handle->search(
2075 base => $ldap_base,
2076 scope => 'sub',
2077 attrs => ['FAIrepository', 'gosaUnitTag'],
2078 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2079 );
2080 if($mesg->{'resultCode'} == 0 &&
2081 $mesg->count != 0) {
2082 foreach my $entry (@{$mesg->{entries}}) {
2083 if($entry->exists('FAIrepository')) {
2084 # Add an entry for each Repository configured for server
2085 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2086 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2087 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2088 $result= $fai_server_db->add_dbentry( {
2089 table => $table_name,
2090 primkey => ['server', 'release', 'tag'],
2091 server => $tmp_url,
2092 release => $tmp_release,
2093 sections => $tmp_sections,
2094 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2095 } );
2096 }
2097 }
2098 }
2099 }
2100 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2102 # TODO: Find a way to post the 'create_packages_list_db' event
2103 if(not defined($dont_create_packages_list)) {
2104 &create_packages_list_db(undef, undef, $session_id);
2105 }
2106 }
2108 $ldap_handle->disconnect;
2109 return $result;
2110 }
2113 sub run_create_fai_release_db {
2114 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2115 my $session_id = $session->ID;
2116 my $task = POE::Wheel::Run->new(
2117 Program => sub { &create_fai_release_db($table_name, $session_id) },
2118 StdoutEvent => "session_run_result",
2119 StderrEvent => "session_run_debug",
2120 CloseEvent => "session_run_done",
2121 );
2123 $heap->{task}->{ $task->ID } = $task;
2124 return;
2125 }
2128 sub create_fai_release_db {
2129 my ($table_name, $session_id) = @_;
2130 my $result;
2132 # used for logging
2133 if (not defined $session_id) { $session_id = 0; }
2135 my $ldap_handle = &get_ldap_handle();
2136 if(defined($ldap_handle)) {
2137 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2138 my $mesg= $ldap_handle->search(
2139 base => $ldap_base,
2140 scope => 'sub',
2141 attrs => [],
2142 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2143 );
2144 if($mesg->{'resultCode'} == 0 &&
2145 $mesg->count != 0) {
2146 # Walk through all possible FAI container ou's
2147 my @sql_list;
2148 my $timestamp= &get_time();
2149 foreach my $ou (@{$mesg->{entries}}) {
2150 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2151 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2152 my @tmp_array=get_fai_release_entries($tmp_classes);
2153 if(@tmp_array) {
2154 foreach my $entry (@tmp_array) {
2155 if(defined($entry) && ref($entry) eq 'HASH') {
2156 my $sql=
2157 "INSERT INTO $table_name "
2158 ."(timestamp, release, class, type, state) VALUES ("
2159 .$timestamp.","
2160 ."'".$entry->{'release'}."',"
2161 ."'".$entry->{'class'}."',"
2162 ."'".$entry->{'type'}."',"
2163 ."'".$entry->{'state'}."')";
2164 push @sql_list, $sql;
2165 }
2166 }
2167 }
2168 }
2169 }
2171 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2172 if(@sql_list) {
2173 unshift @sql_list, "VACUUM";
2174 unshift @sql_list, "DELETE FROM $table_name";
2175 $fai_release_db->exec_statementlist(\@sql_list);
2176 }
2177 daemon_log("$session_id DEBUG: Done with inserting",7);
2178 }
2179 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2180 }
2181 $ldap_handle->disconnect;
2182 return $result;
2183 }
2185 sub get_fai_types {
2186 my $tmp_classes = shift || return undef;
2187 my @result;
2189 foreach my $type(keys %{$tmp_classes}) {
2190 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2191 my $entry = {
2192 type => $type,
2193 state => $tmp_classes->{$type}[0],
2194 };
2195 push @result, $entry;
2196 }
2197 }
2199 return @result;
2200 }
2202 sub get_fai_state {
2203 my $result = "";
2204 my $tmp_classes = shift || return $result;
2206 foreach my $type(keys %{$tmp_classes}) {
2207 if(defined($tmp_classes->{$type}[0])) {
2208 $result = $tmp_classes->{$type}[0];
2210 # State is equal for all types in class
2211 last;
2212 }
2213 }
2215 return $result;
2216 }
2218 sub resolve_fai_classes {
2219 my ($fai_base, $ldap_handle, $session_id) = @_;
2220 if (not defined $session_id) { $session_id = 0; }
2221 my $result;
2222 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2223 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2224 my $fai_classes;
2226 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2227 my $mesg= $ldap_handle->search(
2228 base => $fai_base,
2229 scope => 'sub',
2230 attrs => ['cn','objectClass','FAIstate'],
2231 filter => $fai_filter,
2232 );
2233 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2235 if($mesg->{'resultCode'} == 0 &&
2236 $mesg->count != 0) {
2237 foreach my $entry (@{$mesg->{entries}}) {
2238 if($entry->exists('cn')) {
2239 my $tmp_dn= $entry->dn();
2241 # Skip classname and ou dn parts for class
2242 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2244 # Skip classes without releases
2245 if((!defined($tmp_release)) || length($tmp_release)==0) {
2246 next;
2247 }
2249 my $tmp_cn= $entry->get_value('cn');
2250 my $tmp_state= $entry->get_value('FAIstate');
2252 my $tmp_type;
2253 # Get FAI type
2254 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2255 if(grep $_ eq $oclass, @possible_fai_classes) {
2256 $tmp_type= $oclass;
2257 last;
2258 }
2259 }
2261 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2262 # A Subrelease
2263 my @sub_releases = split(/,/, $tmp_release);
2265 # Walk through subreleases and build hash tree
2266 my $hash;
2267 while(my $tmp_sub_release = pop @sub_releases) {
2268 $hash .= "\{'$tmp_sub_release'\}->";
2269 }
2270 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2271 } else {
2272 # A branch, no subrelease
2273 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2274 }
2275 } elsif (!$entry->exists('cn')) {
2276 my $tmp_dn= $entry->dn();
2277 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2279 # Skip classes without releases
2280 if((!defined($tmp_release)) || length($tmp_release)==0) {
2281 next;
2282 }
2284 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2285 # A Subrelease
2286 my @sub_releases= split(/,/, $tmp_release);
2288 # Walk through subreleases and build hash tree
2289 my $hash;
2290 while(my $tmp_sub_release = pop @sub_releases) {
2291 $hash .= "\{'$tmp_sub_release'\}->";
2292 }
2293 # Remove the last two characters
2294 chop($hash);
2295 chop($hash);
2297 eval('$fai_classes->'.$hash.'= {}');
2298 } else {
2299 # A branch, no subrelease
2300 if(!exists($fai_classes->{$tmp_release})) {
2301 $fai_classes->{$tmp_release} = {};
2302 }
2303 }
2304 }
2305 }
2307 # The hash is complete, now we can honor the copy-on-write based missing entries
2308 foreach my $release (keys %$fai_classes) {
2309 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2310 }
2311 }
2312 return $result;
2313 }
2315 sub apply_fai_inheritance {
2316 my $fai_classes = shift || return {};
2317 my $tmp_classes;
2319 # Get the classes from the branch
2320 foreach my $class (keys %{$fai_classes}) {
2321 # Skip subreleases
2322 if($class =~ /^ou=.*$/) {
2323 next;
2324 } else {
2325 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2326 }
2327 }
2329 # Apply to each subrelease
2330 foreach my $subrelease (keys %{$fai_classes}) {
2331 if($subrelease =~ /ou=/) {
2332 foreach my $tmp_class (keys %{$tmp_classes}) {
2333 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2334 $fai_classes->{$subrelease}->{$tmp_class} =
2335 deep_copy($tmp_classes->{$tmp_class});
2336 } else {
2337 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2338 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2339 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2340 deep_copy($tmp_classes->{$tmp_class}->{$type});
2341 }
2342 }
2343 }
2344 }
2345 }
2346 }
2348 # Find subreleases in deeper levels
2349 foreach my $subrelease (keys %{$fai_classes}) {
2350 if($subrelease =~ /ou=/) {
2351 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2352 if($subsubrelease =~ /ou=/) {
2353 apply_fai_inheritance($fai_classes->{$subrelease});
2354 }
2355 }
2356 }
2357 }
2359 return $fai_classes;
2360 }
2362 sub get_fai_release_entries {
2363 my $tmp_classes = shift || return;
2364 my $parent = shift || "";
2365 my @result = shift || ();
2367 foreach my $entry (keys %{$tmp_classes}) {
2368 if(defined($entry)) {
2369 if($entry =~ /^ou=.*$/) {
2370 my $release_name = $entry;
2371 $release_name =~ s/ou=//g;
2372 if(length($parent)>0) {
2373 $release_name = $parent."/".$release_name;
2374 }
2375 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2376 foreach my $bufentry(@bufentries) {
2377 push @result, $bufentry;
2378 }
2379 } else {
2380 my @types = get_fai_types($tmp_classes->{$entry});
2381 foreach my $type (@types) {
2382 push @result,
2383 {
2384 'class' => $entry,
2385 'type' => $type->{'type'},
2386 'release' => $parent,
2387 'state' => $type->{'state'},
2388 };
2389 }
2390 }
2391 }
2392 }
2394 return @result;
2395 }
2397 sub deep_copy {
2398 my $this = shift;
2399 if (not ref $this) {
2400 $this;
2401 } elsif (ref $this eq "ARRAY") {
2402 [map deep_copy($_), @$this];
2403 } elsif (ref $this eq "HASH") {
2404 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2405 } else { die "what type is $_?" }
2406 }
2409 sub session_run_result {
2410 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2411 $kernel->sig(CHLD => "child_reap");
2412 }
2414 sub session_run_debug {
2415 my $result = $_[ARG0];
2416 print STDERR "$result\n";
2417 }
2419 sub session_run_done {
2420 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2421 delete $heap->{task}->{$task_id};
2422 }
2425 sub create_sources_list {
2426 my $session_id = shift;
2427 my $ldap_handle = &main::get_ldap_handle;
2428 my $result="/tmp/gosa_si_tmp_sources_list";
2430 # Remove old file
2431 if(stat($result)) {
2432 unlink($result);
2433 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2434 }
2436 my $fh;
2437 open($fh, ">$result");
2438 if (not defined $fh) {
2439 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2440 return undef;
2441 }
2442 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2443 my $mesg=$ldap_handle->search(
2444 base => $main::ldap_server_dn,
2445 scope => 'base',
2446 attrs => 'FAIrepository',
2447 filter => 'objectClass=FAIrepositoryServer'
2448 );
2449 if($mesg->count) {
2450 foreach my $entry(@{$mesg->{'entries'}}) {
2451 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2452 my ($server, $tag, $release, $sections)= split /\|/, $value;
2453 my $line = "deb $server $release";
2454 $sections =~ s/,/ /g;
2455 $line.= " $sections";
2456 print $fh $line."\n";
2457 }
2458 }
2459 }
2460 } else {
2461 if (defined $main::ldap_server_dn){
2462 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2463 } else {
2464 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2465 }
2466 }
2467 close($fh);
2469 return $result;
2470 }
2473 sub run_create_packages_list_db {
2474 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2475 my $session_id = $session->ID;
2477 my $task = POE::Wheel::Run->new(
2478 Priority => +20,
2479 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2480 StdoutEvent => "session_run_result",
2481 StderrEvent => "session_run_debug",
2482 CloseEvent => "session_run_done",
2483 );
2484 $heap->{task}->{ $task->ID } = $task;
2485 }
2488 sub create_packages_list_db {
2489 my ($ldap_handle, $sources_file, $session_id) = @_;
2491 # it should not be possible to trigger a recreation of packages_list_db
2492 # while packages_list_db is under construction, so set flag packages_list_under_construction
2493 # which is tested befor recreation can be started
2494 if (-r $packages_list_under_construction) {
2495 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2496 return;
2497 } else {
2498 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2499 # set packages_list_under_construction to true
2500 system("touch $packages_list_under_construction");
2501 @packages_list_statements=();
2502 }
2504 if (not defined $session_id) { $session_id = 0; }
2505 if (not defined $ldap_handle) {
2506 $ldap_handle= &get_ldap_handle();
2508 if (not defined $ldap_handle) {
2509 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2510 unlink($packages_list_under_construction);
2511 return;
2512 }
2513 }
2514 if (not defined $sources_file) {
2515 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2516 $sources_file = &create_sources_list($session_id);
2517 }
2519 if (not defined $sources_file) {
2520 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2521 unlink($packages_list_under_construction);
2522 return;
2523 }
2525 my $line;
2527 open(CONFIG, "<$sources_file") or do {
2528 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2529 unlink($packages_list_under_construction);
2530 return;
2531 };
2533 # Read lines
2534 while ($line = <CONFIG>){
2535 # Unify
2536 chop($line);
2537 $line =~ s/^\s+//;
2538 $line =~ s/^\s+/ /;
2540 # Strip comments
2541 $line =~ s/#.*$//g;
2543 # Skip empty lines
2544 if ($line =~ /^\s*$/){
2545 next;
2546 }
2548 # Interpret deb line
2549 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2550 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2551 my $section;
2552 foreach $section (split(' ', $sections)){
2553 &parse_package_info( $baseurl, $dist, $section, $session_id );
2554 }
2555 }
2556 }
2558 close (CONFIG);
2560 find(\&cleanup_and_extract, keys( %repo_dirs ));
2561 &main::strip_packages_list_statements();
2562 unshift @packages_list_statements, "VACUUM";
2563 $packages_list_db->exec_statementlist(\@packages_list_statements);
2564 unlink($packages_list_under_construction);
2565 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2566 return;
2567 }
2569 # This function should do some intensive task to minimize the db-traffic
2570 sub strip_packages_list_statements {
2571 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2572 my @new_statement_list=();
2573 my $hash;
2574 my $insert_hash;
2575 my $update_hash;
2576 my $delete_hash;
2577 my $local_timestamp=get_time();
2579 foreach my $existing_entry (@existing_entries) {
2580 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2581 }
2583 foreach my $statement (@packages_list_statements) {
2584 if($statement =~ /^INSERT/i) {
2585 # Assign the values from the insert statement
2586 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2587 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2588 if(exists($hash->{$distribution}->{$package}->{$version})) {
2589 # If section or description has changed, update the DB
2590 if(
2591 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2592 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2593 ) {
2594 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2595 }
2596 } else {
2597 # Insert a non-existing entry to db
2598 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2599 }
2600 } elsif ($statement =~ /^UPDATE/i) {
2601 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2602 /^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;
2603 foreach my $distribution (keys %{$hash}) {
2604 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2605 # update the insertion hash to execute only one query per package (insert instead insert+update)
2606 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2607 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2608 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2609 my $section;
2610 my $description;
2611 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2612 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2613 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2614 }
2615 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2616 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2617 }
2618 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2619 }
2620 }
2621 }
2622 }
2623 }
2625 # TODO: Check for orphaned entries
2627 # unroll the insert_hash
2628 foreach my $distribution (keys %{$insert_hash}) {
2629 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2630 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2631 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2632 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2633 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2634 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2635 ."'$local_timestamp')";
2636 }
2637 }
2638 }
2640 # unroll the update hash
2641 foreach my $distribution (keys %{$update_hash}) {
2642 foreach my $package (keys %{$update_hash->{$distribution}}) {
2643 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2644 my $set = "";
2645 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2646 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2647 }
2648 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2649 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2650 }
2651 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2652 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2653 }
2654 if(defined($set) and length($set) > 0) {
2655 $set .= "timestamp = '$local_timestamp'";
2656 } else {
2657 next;
2658 }
2659 push @new_statement_list,
2660 "UPDATE $main::packages_list_tn SET $set WHERE"
2661 ." distribution = '$distribution'"
2662 ." AND package = '$package'"
2663 ." AND version = '$version'";
2664 }
2665 }
2666 }
2668 @packages_list_statements = @new_statement_list;
2669 }
2672 sub parse_package_info {
2673 my ($baseurl, $dist, $section, $session_id)= @_;
2674 my ($package);
2675 if (not defined $session_id) { $session_id = 0; }
2676 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2677 $repo_dirs{ "${repo_path}/pool" } = 1;
2679 foreach $package ("Packages.gz"){
2680 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2681 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2682 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2683 }
2685 }
2688 sub get_package {
2689 my ($url, $dest, $session_id)= @_;
2690 if (not defined $session_id) { $session_id = 0; }
2692 my $tpath = dirname($dest);
2693 -d "$tpath" || mkpath "$tpath";
2695 # This is ugly, but I've no time to take a look at "how it works in perl"
2696 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2697 system("gunzip -cd '$dest' > '$dest.in'");
2698 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2699 unlink($dest);
2700 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2701 } else {
2702 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2703 }
2704 return 0;
2705 }
2708 sub parse_package {
2709 my ($path, $dist, $srv_path, $session_id)= @_;
2710 if (not defined $session_id) { $session_id = 0;}
2711 my ($package, $version, $section, $description);
2712 my $PACKAGES;
2713 my $timestamp = &get_time();
2715 if(not stat("$path.in")) {
2716 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2717 return;
2718 }
2720 open($PACKAGES, "<$path.in");
2721 if(not defined($PACKAGES)) {
2722 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2723 return;
2724 }
2726 # Read lines
2727 while (<$PACKAGES>){
2728 my $line = $_;
2729 # Unify
2730 chop($line);
2732 # Use empty lines as a trigger
2733 if ($line =~ /^\s*$/){
2734 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2735 push(@packages_list_statements, $sql);
2736 $package = "none";
2737 $version = "none";
2738 $section = "none";
2739 $description = "none";
2740 next;
2741 }
2743 # Trigger for package name
2744 if ($line =~ /^Package:\s/){
2745 ($package)= ($line =~ /^Package: (.*)$/);
2746 next;
2747 }
2749 # Trigger for version
2750 if ($line =~ /^Version:\s/){
2751 ($version)= ($line =~ /^Version: (.*)$/);
2752 next;
2753 }
2755 # Trigger for description
2756 if ($line =~ /^Description:\s/){
2757 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2758 next;
2759 }
2761 # Trigger for section
2762 if ($line =~ /^Section:\s/){
2763 ($section)= ($line =~ /^Section: (.*)$/);
2764 next;
2765 }
2767 # Trigger for filename
2768 if ($line =~ /^Filename:\s/){
2769 my ($filename) = ($line =~ /^Filename: (.*)$/);
2770 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2771 next;
2772 }
2773 }
2775 close( $PACKAGES );
2776 unlink( "$path.in" );
2777 }
2780 sub store_fileinfo {
2781 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2783 my %fileinfo = (
2784 'package' => $package,
2785 'dist' => $dist,
2786 'version' => $vers,
2787 );
2789 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2790 }
2793 sub cleanup_and_extract {
2794 my $fileinfo = $repo_files{ $File::Find::name };
2796 if( defined $fileinfo ) {
2798 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2799 my $sql;
2800 my $package = $fileinfo->{ 'package' };
2801 my $newver = $fileinfo->{ 'version' };
2803 mkpath($dir);
2804 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2806 if( -f "$dir/DEBIAN/templates" ) {
2808 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2810 my $tmpl= "";
2811 {
2812 local $/=undef;
2813 open FILE, "$dir/DEBIAN/templates";
2814 $tmpl = &encode_base64(<FILE>);
2815 close FILE;
2816 }
2817 rmtree("$dir/DEBIAN/templates");
2819 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2820 push @packages_list_statements, $sql;
2821 }
2822 }
2824 return;
2825 }
2828 sub register_at_foreign_servers {
2829 my ($kernel) = $_[KERNEL];
2831 # hole alle bekannten server aus known_server_db
2832 my $server_sql = "SELECT * FROM $known_server_tn";
2833 my $server_res = $known_server_db->exec_statement($server_sql);
2835 # no entries in known_server_db
2836 if (not ref(@$server_res[0]) eq "ARRAY") {
2837 # TODO
2838 }
2840 # detect already connected clients
2841 my $client_sql = "SELECT * FROM $known_clients_tn";
2842 my $client_res = $known_clients_db->exec_statement($client_sql);
2844 # send my server details to all other gosa-si-server within the network
2845 foreach my $hit (@$server_res) {
2846 my $hostname = @$hit[0];
2847 my $hostkey = &create_passwd;
2849 # add already connected clients to registration message
2850 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2851 &add_content2xml_hash($myhash, 'key', $hostkey);
2852 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2854 # build registration message and send it
2855 my $foreign_server_msg = &create_xml_string($myhash);
2856 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2857 }
2859 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2860 return;
2861 }
2864 #==== MAIN = main ==============================================================
2865 # parse commandline options
2866 Getopt::Long::Configure( "bundling" );
2867 GetOptions("h|help" => \&usage,
2868 "c|config=s" => \$cfg_file,
2869 "f|foreground" => \$foreground,
2870 "v|verbose+" => \$verbose,
2871 "no-arp+" => \$no_arp,
2872 );
2874 # read and set config parameters
2875 &check_cmdline_param ;
2876 &read_configfile;
2877 &check_pid;
2879 $SIG{CHLD} = 'IGNORE';
2881 # forward error messages to logfile
2882 if( ! $foreground ) {
2883 open( STDIN, '+>/dev/null' );
2884 open( STDOUT, '+>&STDIN' );
2885 open( STDERR, '+>&STDIN' );
2886 }
2888 # Just fork, if we are not in foreground mode
2889 if( ! $foreground ) {
2890 chdir '/' or die "Can't chdir to /: $!";
2891 $pid = fork;
2892 setsid or die "Can't start a new session: $!";
2893 umask 0;
2894 } else {
2895 $pid = $$;
2896 }
2898 # Do something useful - put our PID into the pid_file
2899 if( 0 != $pid ) {
2900 open( LOCK_FILE, ">$pid_file" );
2901 print LOCK_FILE "$pid\n";
2902 close( LOCK_FILE );
2903 if( !$foreground ) {
2904 exit( 0 )
2905 };
2906 }
2908 # parse head url and revision from svn
2909 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2910 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2911 $server_headURL = defined $1 ? $1 : 'unknown' ;
2912 $server_revision = defined $2 ? $2 : 'unknown' ;
2913 if ($server_headURL =~ /\/tag\// ||
2914 $server_headURL =~ /\/branches\// ) {
2915 $server_status = "stable";
2916 } else {
2917 $server_status = "developmental" ;
2918 }
2921 daemon_log(" ", 1);
2922 daemon_log("$0 started!", 1);
2923 daemon_log("status: $server_status", 1);
2924 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2926 # connect to incoming_db
2927 unlink($incoming_file_name);
2928 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2929 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2931 # connect to gosa-si job queue
2932 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2933 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2935 # connect to known_clients_db
2936 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2937 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2939 # connect to foreign_clients_db
2940 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2941 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2943 # connect to known_server_db
2944 unlink($known_server_file_name);
2945 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2946 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2948 # connect to login_usr_db
2949 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2950 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2952 # connect to fai_server_db and fai_release_db
2953 unlink($fai_server_file_name);
2954 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2955 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2957 unlink($fai_release_file_name);
2958 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2959 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2961 # connect to packages_list_db
2962 #unlink($packages_list_file_name);
2963 unlink($packages_list_under_construction);
2964 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2965 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2967 # connect to messaging_db
2968 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2969 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2972 # create xml object used for en/decrypting
2973 $xml = new XML::Simple();
2976 # foreign servers
2977 my @foreign_server_list;
2979 # add foreign server from cfg file
2980 if ($foreign_server_string ne "") {
2981 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2982 foreach my $foreign_server (@cfg_foreign_server_list) {
2983 push(@foreign_server_list, $foreign_server);
2984 }
2985 }
2987 # add foreign server from dns
2988 my @tmp_servers;
2989 if ( !$server_domain) {
2990 # Try our DNS Searchlist
2991 for my $domain(get_dns_domains()) {
2992 chomp($domain);
2993 my @tmp_domains= &get_server_addresses($domain);
2994 if(@tmp_domains) {
2995 for my $tmp_server(@tmp_domains) {
2996 push @tmp_servers, $tmp_server;
2997 }
2998 }
2999 }
3000 if(@tmp_servers && length(@tmp_servers)==0) {
3001 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3002 }
3003 } else {
3004 @tmp_servers = &get_server_addresses($server_domain);
3005 if( 0 == @tmp_servers ) {
3006 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3007 }
3008 }
3009 foreach my $server (@tmp_servers) {
3010 unshift(@foreign_server_list, $server);
3011 }
3012 # eliminate duplicate entries
3013 @foreign_server_list = &del_doubles(@foreign_server_list);
3014 my $all_foreign_server = join(", ", @foreign_server_list);
3015 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3017 # add all found foreign servers to known_server
3018 my $act_timestamp = &get_time();
3019 foreach my $foreign_server (@foreign_server_list) {
3021 # do not add myself to known_server_db
3022 if (&is_local($foreign_server)) { next; }
3023 ######################################
3025 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3026 primkey=>['hostname'],
3027 hostname=>$foreign_server,
3028 status=>'not_jet_registered',
3029 hostkey=>"none",
3030 timestamp=>$act_timestamp,
3031 } );
3032 }
3035 # import all modules
3036 &import_modules;
3037 # check wether all modules are gosa-si valid passwd check
3038 &password_check;
3041 POE::Component::Server::TCP->new(
3042 Alias => "TCP_SERVER",
3043 Port => $server_port,
3044 ClientInput => sub {
3045 my ($kernel, $input) = @_[KERNEL, ARG0];
3046 push(@tasks, $input);
3047 push(@msgs_to_decrypt, $input);
3048 $kernel->yield("msg_to_decrypt");
3049 },
3050 InlineStates => {
3051 msg_to_decrypt => \&msg_to_decrypt,
3052 next_task => \&next_task,
3053 task_result => \&handle_task_result,
3054 task_done => \&handle_task_done,
3055 task_debug => \&handle_task_debug,
3056 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3057 }
3058 );
3060 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3062 # create session for repeatedly checking the job queue for jobs
3063 POE::Session->create(
3064 inline_states => {
3065 _start => \&session_start,
3066 register_at_foreign_servers => \®ister_at_foreign_servers,
3067 sig_handler => \&sig_handler,
3068 next_task => \&next_task,
3069 task_result => \&handle_task_result,
3070 task_done => \&handle_task_done,
3071 task_debug => \&handle_task_debug,
3072 watch_for_next_tasks => \&watch_for_next_tasks,
3073 watch_for_new_messages => \&watch_for_new_messages,
3074 watch_for_delivery_messages => \&watch_for_delivery_messages,
3075 watch_for_done_messages => \&watch_for_done_messages,
3076 watch_for_new_jobs => \&watch_for_new_jobs,
3077 watch_for_modified_jobs => \&watch_for_modified_jobs,
3078 watch_for_done_jobs => \&watch_for_done_jobs,
3079 watch_for_old_known_clients => \&watch_for_old_known_clients,
3080 create_packages_list_db => \&run_create_packages_list_db,
3081 create_fai_server_db => \&run_create_fai_server_db,
3082 create_fai_release_db => \&run_create_fai_release_db,
3083 recreate_packages_db => \&run_recreate_packages_db,
3084 session_run_result => \&session_run_result,
3085 session_run_debug => \&session_run_debug,
3086 session_run_done => \&session_run_done,
3087 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3088 }
3089 );
3092 POE::Kernel->run();
3093 exit;