831ca6f550d44c6b7f220b3f832785c9ffeeea97
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 }
1164 $done = 1;
1165 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1166 }
1167 }
1169 }
1171 # target is a client address in foreign_clients -> forward to registration server
1172 if (not $done) {
1173 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1174 $res = $foreign_clients_db->select_dbentry($sql);
1175 if (keys(%$res) > 0) {
1176 my $hostname = $res->{1}->{'hostname'};
1177 my ($host_ip, $host_port) = split(/:/, $hostname);
1178 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1179 my $regserver = $res->{1}->{'regserver'};
1180 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1181 my $res = $known_server_db->select_dbentry($sql);
1182 if (keys(%$res) > 0) {
1183 my $regserver_key = $res->{1}->{'hostkey'};
1184 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1185 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1186 if ($source eq "GOSA") {
1187 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1188 }
1189 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1190 }
1191 $done = 1;
1192 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1193 } else {
1194 $not_found_in_foreign_clients_db = 1;
1195 }
1196 }
1198 # target is a server address -> forward to server
1199 if (not $done) {
1200 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1201 $res = $known_server_db->select_dbentry($sql);
1202 if (keys(%$res) > 0) {
1203 my $hostkey = $res->{1}->{'hostkey'};
1205 if ($source eq "GOSA") {
1206 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1207 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1209 }
1211 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1212 $done = 1;
1213 #print STDERR "target is a server address -> forward to server\n";
1214 } else {
1215 $not_found_in_known_server_db = 1;
1216 }
1217 }
1220 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1221 if ( $not_found_in_foreign_clients_db
1222 && $not_found_in_known_server_db
1223 && $not_found_in_known_clients_db) {
1224 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1225 primkey=>[],
1226 headertag=>$header,
1227 targettag=>$target,
1228 xmlmessage=>&encode_base64($msg),
1229 timestamp=>&get_time,
1230 module=>$module,
1231 sessionid=>$session_id,
1232 } );
1233 $done = 1;
1234 }
1237 if (not $done) {
1238 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1239 if ($source eq "GOSA") {
1240 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1241 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1243 my $session_reference = $kernel->ID_id_to_session($session_id);
1244 if( defined $session_reference ) {
1245 $heap = $session_reference->get_heap();
1246 }
1247 if(exists $heap->{'client'}) {
1248 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1249 $heap->{'client'}->put($error_msg);
1250 }
1251 }
1252 }
1254 }
1256 return;
1257 }
1260 sub next_task {
1261 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1262 my $running_task = POE::Wheel::Run->new(
1263 Program => sub { process_task($session, $heap, $task) },
1264 StdioFilter => POE::Filter::Reference->new(),
1265 StdoutEvent => "task_result",
1266 StderrEvent => "task_debug",
1267 CloseEvent => "task_done",
1268 );
1269 $heap->{task}->{ $running_task->ID } = $running_task;
1270 }
1272 sub handle_task_result {
1273 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1274 my $client_answer = $result->{'answer'};
1275 if( $client_answer =~ s/session_id=(\d+)$// ) {
1276 my $session_id = $1;
1277 if( defined $session_id ) {
1278 my $session_reference = $kernel->ID_id_to_session($session_id);
1279 if( defined $session_reference ) {
1280 $heap = $session_reference->get_heap();
1281 }
1282 }
1284 if(exists $heap->{'client'}) {
1285 $heap->{'client'}->put($client_answer);
1286 }
1287 }
1288 $kernel->sig(CHLD => "child_reap");
1289 }
1291 sub handle_task_debug {
1292 my $result = $_[ARG0];
1293 print STDERR "$result\n";
1294 }
1296 sub handle_task_done {
1297 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1298 delete $heap->{task}->{$task_id};
1299 }
1301 sub process_task {
1302 no strict "refs";
1303 #CHECK: Not @_[...]?
1304 my ($session, $heap, $task) = @_;
1305 my $error = 0;
1306 my $answer_l;
1307 my ($answer_header, @answer_target_l, $answer_source);
1308 my $client_answer = "";
1310 # prepare all variables needed to process message
1311 #my $msg = $task->{'xmlmessage'};
1312 my $msg = &decode_base64($task->{'xmlmessage'});
1313 my $incoming_id = $task->{'id'};
1314 my $module = $task->{'module'};
1315 my $header = $task->{'headertag'};
1316 my $session_id = $task->{'sessionid'};
1317 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1318 my $source = @{$msg_hash->{'source'}}[0];
1320 # set timestamp of incoming client uptodate, so client will not
1321 # be deleted from known_clients because of expiration
1322 my $act_time = &get_time();
1323 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1324 my $res = $known_clients_db->exec_statement($sql);
1326 ######################
1327 # process incoming msg
1328 if( $error == 0) {
1329 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1330 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1331 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1333 if ( 0 < @{$answer_l} ) {
1334 my $answer_str = join("\n", @{$answer_l});
1335 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1336 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1337 }
1338 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1339 } else {
1340 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1341 }
1343 }
1344 if( !$answer_l ) { $error++ };
1346 ########
1347 # answer
1348 if( $error == 0 ) {
1350 foreach my $answer ( @{$answer_l} ) {
1351 # check outgoing msg to xml validity
1352 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1353 if( not defined $answer_hash ) { next; }
1355 $answer_header = @{$answer_hash->{'header'}}[0];
1356 @answer_target_l = @{$answer_hash->{'target'}};
1357 $answer_source = @{$answer_hash->{'source'}}[0];
1359 # deliver msg to all targets
1360 foreach my $answer_target ( @answer_target_l ) {
1362 # targets of msg are all gosa-si-clients in known_clients_db
1363 if( $answer_target eq "*" ) {
1364 # answer is for all clients
1365 my $sql_statement= "SELECT * FROM known_clients";
1366 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1367 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1368 my $host_name = $hit->{hostname};
1369 my $host_key = $hit->{hostkey};
1370 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1371 &update_jobdb_status_for_send_msgs($answer, $error);
1372 }
1373 }
1375 # targets of msg are all gosa-si-server in known_server_db
1376 elsif( $answer_target eq "KNOWN_SERVER" ) {
1377 # answer is for all server in known_server
1378 my $sql_statement= "SELECT * FROM $known_server_tn";
1379 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1380 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1381 my $host_name = $hit->{hostname};
1382 my $host_key = $hit->{hostkey};
1383 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1384 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1385 &update_jobdb_status_for_send_msgs($answer, $error);
1386 }
1387 }
1389 # target of msg is GOsa
1390 elsif( $answer_target eq "GOSA" ) {
1391 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1392 my $add_on = "";
1393 if( defined $session_id ) {
1394 $add_on = ".session_id=$session_id";
1395 }
1396 # answer is for GOSA and has to returned to connected client
1397 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1398 $client_answer = $gosa_answer.$add_on;
1399 }
1401 # target of msg is job queue at this host
1402 elsif( $answer_target eq "JOBDB") {
1403 $answer =~ /<header>(\S+)<\/header>/;
1404 my $header;
1405 if( defined $1 ) { $header = $1; }
1406 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1407 &update_jobdb_status_for_send_msgs($answer, $error);
1408 }
1410 # target of msg is a mac address
1411 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 ) {
1412 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1413 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1414 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1415 my $found_ip_flag = 0;
1416 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1417 my $host_name = $hit->{hostname};
1418 my $host_key = $hit->{hostkey};
1419 $answer =~ s/$answer_target/$host_name/g;
1420 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1421 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1422 &update_jobdb_status_for_send_msgs($answer, $error);
1423 $found_ip_flag++ ;
1424 }
1425 if( $found_ip_flag == 0) {
1426 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1427 }
1429 # answer is for one specific host
1430 } else {
1431 # get encrypt_key
1432 my $encrypt_key = &get_encrypt_key($answer_target);
1433 if( not defined $encrypt_key ) {
1434 # unknown target
1435 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1436 next;
1437 }
1438 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1439 &update_jobdb_status_for_send_msgs($answer, $error);
1440 }
1441 }
1442 }
1443 }
1445 my $filter = POE::Filter::Reference->new();
1446 my %result = (
1447 status => "seems ok to me",
1448 answer => $client_answer,
1449 );
1451 my $output = $filter->put( [ \%result ] );
1452 print @$output;
1455 }
1457 sub session_start {
1458 my ($kernel) = $_[KERNEL];
1459 $global_kernel = $kernel;
1460 $kernel->yield('register_at_foreign_servers');
1461 $kernel->yield('create_fai_server_db', $fai_server_tn );
1462 $kernel->yield('create_fai_release_db', $fai_release_tn );
1463 $kernel->yield('watch_for_next_tasks');
1464 $kernel->sig(USR1 => "sig_handler");
1465 $kernel->sig(USR2 => "recreate_packages_db");
1466 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1467 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1468 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1469 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1470 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1471 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1472 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1475 }
1478 sub watch_for_done_jobs {
1479 #CHECK: $heap for what?
1480 my ($kernel,$heap) = @_[KERNEL, HEAP];
1482 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1483 my $res = $job_db->select_dbentry( $sql_statement );
1485 while( my ($id, $hit) = each %{$res} ) {
1486 my $jobdb_id = $hit->{id};
1487 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1488 my $res = $job_db->del_dbentry($sql_statement);
1489 }
1491 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1492 }
1495 # if a job got an update or was modified anyway, send to all other si-server an update message
1496 # of this jobs
1497 sub watch_for_modified_jobs {
1498 my ($kernel,$heap) = @_[KERNEL, HEAP];
1500 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1501 my $res = $job_db->select_dbentry( $sql_statement );
1503 # if db contains no jobs which should be update, do nothing
1504 if (keys %$res != 0) {
1506 if ($job_synchronization eq "true") {
1507 # make out of the db result a gosa-si message
1508 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1510 # update all other SI-server
1511 &inform_all_other_si_server($update_msg);
1512 }
1514 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1515 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1516 $res = $job_db->update_dbentry($sql_statement);
1517 }
1519 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1520 }
1523 sub watch_for_new_jobs {
1524 if($watch_for_new_jobs_in_progress == 0) {
1525 $watch_for_new_jobs_in_progress = 1;
1526 my ($kernel,$heap) = @_[KERNEL, HEAP];
1528 # check gosa job quaeue for jobs with executable timestamp
1529 my $timestamp = &get_time();
1530 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1531 my $res = $job_db->exec_statement( $sql_statement );
1533 # Merge all new jobs that would do the same actions
1534 my @drops;
1535 my $hits;
1536 foreach my $hit (reverse @{$res} ) {
1537 my $macaddress= lc @{$hit}[8];
1538 my $headertag= @{$hit}[5];
1539 if(
1540 defined($hits->{$macaddress}) &&
1541 defined($hits->{$macaddress}->{$headertag}) &&
1542 defined($hits->{$macaddress}->{$headertag}[0])
1543 ) {
1544 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1545 }
1546 $hits->{$macaddress}->{$headertag}= $hit;
1547 }
1549 # Delete new jobs with a matching job in state 'processing'
1550 foreach my $macaddress (keys %{$hits}) {
1551 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1552 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1553 if(defined($jobdb_id)) {
1554 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1555 my $res = $job_db->exec_statement( $sql_statement );
1556 foreach my $hit (@{$res}) {
1557 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1558 }
1559 } else {
1560 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1561 }
1562 }
1563 }
1565 # Commit deletion
1566 $job_db->exec_statementlist(\@drops);
1568 # Look for new jobs that could be executed
1569 foreach my $macaddress (keys %{$hits}) {
1571 # Look if there is an executing job
1572 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1573 my $res = $job_db->exec_statement( $sql_statement );
1575 # Skip new jobs for host if there is a processing job
1576 if(defined($res) and defined @{$res}[0]) {
1577 next;
1578 }
1580 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1581 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1582 if(defined($jobdb_id)) {
1583 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1585 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1586 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1587 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1589 # expect macaddress is unique!!!!!!
1590 my $target = $res_hash->{1}->{hostname};
1592 # change header
1593 $job_msg =~ s/<header>job_/<header>gosa_/;
1595 # add sqlite_id
1596 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1598 $job_msg =~ /<header>(\S+)<\/header>/;
1599 my $header = $1 ;
1600 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1602 # update status in job queue to 'processing'
1603 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1604 my $res = $job_db->update_dbentry($sql_statement);
1605 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1607 # We don't want parallel processing
1608 last;
1609 }
1610 }
1611 }
1613 $watch_for_new_jobs_in_progress = 0;
1614 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1615 }
1616 }
1620 sub watch_for_new_messages {
1621 my ($kernel,$heap) = @_[KERNEL, HEAP];
1622 my @coll_user_msg; # collection list of outgoing messages
1624 # check messaging_db for new incoming messages with executable timestamp
1625 my $timestamp = &get_time();
1626 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1627 my $res = $messaging_db->exec_statement( $sql_statement );
1628 foreach my $hit (@{$res}) {
1630 # create outgoing messages
1631 my $message_to = @{$hit}[3];
1632 # translate message_to to plain login name
1633 my @message_to_l = split(/,/, $message_to);
1634 my %receiver_h;
1635 foreach my $receiver (@message_to_l) {
1636 if ($receiver =~ /^u_([\s\S]*)$/) {
1637 $receiver_h{$1} = 0;
1638 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1639 my $group_name = $1;
1640 # fetch all group members from ldap and add them to receiver hash
1641 my $ldap_handle = &get_ldap_handle();
1642 if (defined $ldap_handle) {
1643 my $mesg = $ldap_handle->search(
1644 base => $ldap_base,
1645 scope => 'sub',
1646 attrs => ['memberUid'],
1647 filter => "cn=$group_name",
1648 );
1649 if ($mesg->count) {
1650 my @entries = $mesg->entries;
1651 foreach my $entry (@entries) {
1652 my @receivers= $entry->get_value("memberUid");
1653 foreach my $receiver (@receivers) {
1654 $receiver_h{$1} = 0;
1655 }
1656 }
1657 }
1658 # translating errors ?
1659 if ($mesg->code) {
1660 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1661 }
1662 # ldap handle error ?
1663 } else {
1664 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1665 }
1666 } else {
1667 my $sbjct = &encode_base64(@{$hit}[1]);
1668 my $msg = &encode_base64(@{$hit}[7]);
1669 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1670 }
1671 }
1672 my @receiver_l = keys(%receiver_h);
1674 my $message_id = @{$hit}[0];
1676 #add each outgoing msg to messaging_db
1677 my $receiver;
1678 foreach $receiver (@receiver_l) {
1679 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1680 "VALUES ('".
1681 $message_id."', '". # id
1682 @{$hit}[1]."', '". # subject
1683 @{$hit}[2]."', '". # message_from
1684 $receiver."', '". # message_to
1685 "none"."', '". # flag
1686 "out"."', '". # direction
1687 @{$hit}[6]."', '". # delivery_time
1688 @{$hit}[7]."', '". # message
1689 $timestamp."'". # timestamp
1690 ")";
1691 &daemon_log("M DEBUG: $sql_statement", 1);
1692 my $res = $messaging_db->exec_statement($sql_statement);
1693 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1694 }
1696 # set incoming message to flag d=deliverd
1697 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1698 &daemon_log("M DEBUG: $sql_statement", 7);
1699 $res = $messaging_db->update_dbentry($sql_statement);
1700 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1701 }
1703 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1704 return;
1705 }
1707 sub watch_for_delivery_messages {
1708 my ($kernel, $heap) = @_[KERNEL, HEAP];
1710 # select outgoing messages
1711 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1712 #&daemon_log("0 DEBUG: $sql", 7);
1713 my $res = $messaging_db->exec_statement( $sql_statement );
1715 # build out msg for each usr
1716 foreach my $hit (@{$res}) {
1717 my $receiver = @{$hit}[3];
1718 my $msg_id = @{$hit}[0];
1719 my $subject = @{$hit}[1];
1720 my $message = @{$hit}[7];
1722 # resolve usr -> host where usr is logged in
1723 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1724 #&daemon_log("0 DEBUG: $sql", 7);
1725 my $res = $login_users_db->exec_statement($sql);
1727 # reciver is logged in nowhere
1728 if (not ref(@$res[0]) eq "ARRAY") { next; }
1730 my $send_succeed = 0;
1731 foreach my $hit (@$res) {
1732 my $receiver_host = @$hit[0];
1733 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1735 # fetch key to encrypt msg propperly for usr/host
1736 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1737 &daemon_log("0 DEBUG: $sql", 7);
1738 my $res = $known_clients_db->exec_statement($sql);
1740 # host is already down
1741 if (not ref(@$res[0]) eq "ARRAY") { next; }
1743 # host is on
1744 my $receiver_key = @{@{$res}[0]}[2];
1745 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1746 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1747 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1748 if ($error == 0 ) {
1749 $send_succeed++ ;
1750 }
1751 }
1753 if ($send_succeed) {
1754 # set outgoing msg at db to deliverd
1755 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1756 &daemon_log("0 DEBUG: $sql", 7);
1757 my $res = $messaging_db->exec_statement($sql);
1758 }
1759 }
1761 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1762 return;
1763 }
1766 sub watch_for_done_messages {
1767 my ($kernel,$heap) = @_[KERNEL, HEAP];
1769 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1770 #&daemon_log("0 DEBUG: $sql", 7);
1771 my $res = $messaging_db->exec_statement($sql);
1773 foreach my $hit (@{$res}) {
1774 my $msg_id = @{$hit}[0];
1776 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1777 #&daemon_log("0 DEBUG: $sql", 7);
1778 my $res = $messaging_db->exec_statement($sql);
1780 # not all usr msgs have been seen till now
1781 if ( ref(@$res[0]) eq "ARRAY") { next; }
1783 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1784 #&daemon_log("0 DEBUG: $sql", 7);
1785 $res = $messaging_db->exec_statement($sql);
1787 }
1789 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1790 return;
1791 }
1794 sub watch_for_old_known_clients {
1795 my ($kernel,$heap) = @_[KERNEL, HEAP];
1797 my $sql_statement = "SELECT * FROM $known_clients_tn";
1798 my $res = $known_clients_db->select_dbentry( $sql_statement );
1800 my $act_time = int(&get_time());
1802 while ( my ($hit_num, $hit) = each %$res) {
1803 my $expired_timestamp = int($hit->{'timestamp'});
1804 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1805 my $dt = DateTime->new( year => $1,
1806 month => $2,
1807 day => $3,
1808 hour => $4,
1809 minute => $5,
1810 second => $6,
1811 );
1813 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1814 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1815 if ($act_time > $expired_timestamp) {
1816 my $hostname = $hit->{'hostname'};
1817 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1818 my $del_res = $known_clients_db->exec_statement($del_sql);
1820 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1821 }
1823 }
1825 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1826 }
1829 sub watch_for_next_tasks {
1830 my ($kernel,$heap) = @_[KERNEL, HEAP];
1832 my $sql = "SELECT * FROM $incoming_tn";
1833 my $res = $incoming_db->select_dbentry($sql);
1835 while ( my ($hit_num, $hit) = each %$res) {
1836 my $headertag = $hit->{'headertag'};
1837 if ($headertag =~ /^answer_(\d+)/) {
1838 # do not start processing, this message is for a still running POE::Wheel
1839 next;
1840 }
1841 my $message_id = $hit->{'id'};
1842 $kernel->yield('next_task', $hit);
1844 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1845 my $res = $incoming_db->exec_statement($sql);
1846 }
1848 $kernel->delay_set('watch_for_next_tasks', 0.1);
1849 }
1852 sub get_ldap_handle {
1853 my ($session_id) = @_;
1854 my $heap;
1855 my $ldap_handle;
1857 if (not defined $session_id ) { $session_id = 0 };
1858 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1860 if ($session_id == 0) {
1861 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1862 $ldap_handle = Net::LDAP->new( $ldap_uri );
1863 $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!");
1865 } else {
1866 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1867 if( defined $session_reference ) {
1868 $heap = $session_reference->get_heap();
1869 }
1871 if (not defined $heap) {
1872 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1873 return;
1874 }
1876 # TODO: This "if" is nonsense, because it doesn't prove that the
1877 # used handle is still valid - or if we've to reconnect...
1878 #if (not exists $heap->{ldap_handle}) {
1879 $ldap_handle = Net::LDAP->new( $ldap_uri );
1880 $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!");
1881 $heap->{ldap_handle} = $ldap_handle;
1882 #}
1883 }
1884 return $ldap_handle;
1885 }
1888 sub change_fai_state {
1889 my ($st, $targets, $session_id) = @_;
1890 $session_id = 0 if not defined $session_id;
1891 # Set FAI state to localboot
1892 my %mapActions= (
1893 reboot => '',
1894 update => 'softupdate',
1895 localboot => 'localboot',
1896 reinstall => 'install',
1897 rescan => '',
1898 wake => '',
1899 memcheck => 'memcheck',
1900 sysinfo => 'sysinfo',
1901 install => 'install',
1902 );
1904 # Return if this is unknown
1905 if (!exists $mapActions{ $st }){
1906 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1907 return;
1908 }
1910 my $state= $mapActions{ $st };
1912 my $ldap_handle = &get_ldap_handle($session_id);
1913 if( defined($ldap_handle) ) {
1915 # Build search filter for hosts
1916 my $search= "(&(objectClass=GOhard)";
1917 foreach (@{$targets}){
1918 $search.= "(macAddress=$_)";
1919 }
1920 $search.= ")";
1922 # If there's any host inside of the search string, procress them
1923 if (!($search =~ /macAddress/)){
1924 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1925 return;
1926 }
1928 # Perform search for Unit Tag
1929 my $mesg = $ldap_handle->search(
1930 base => $ldap_base,
1931 scope => 'sub',
1932 attrs => ['dn', 'FAIstate', 'objectClass'],
1933 filter => "$search"
1934 );
1936 if ($mesg->count) {
1937 my @entries = $mesg->entries;
1938 if (0 == @entries) {
1939 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1940 }
1942 foreach my $entry (@entries) {
1943 # Only modify entry if it is not set to '$state'
1944 if ($entry->get_value("FAIstate") ne "$state"){
1945 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1946 my $result;
1947 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1948 if (exists $tmp{'FAIobject'}){
1949 if ($state eq ''){
1950 $result= $ldap_handle->modify($entry->dn, changes => [
1951 delete => [ FAIstate => [] ] ]);
1952 } else {
1953 $result= $ldap_handle->modify($entry->dn, changes => [
1954 replace => [ FAIstate => $state ] ]);
1955 }
1956 } elsif ($state ne ''){
1957 $result= $ldap_handle->modify($entry->dn, changes => [
1958 add => [ objectClass => 'FAIobject' ],
1959 add => [ FAIstate => $state ] ]);
1960 }
1962 # Errors?
1963 if ($result->code){
1964 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1965 }
1966 } else {
1967 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1968 }
1969 }
1970 } else {
1971 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1972 }
1974 # if no ldap handle defined
1975 } else {
1976 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1977 }
1979 return;
1980 }
1983 sub change_goto_state {
1984 my ($st, $targets, $session_id) = @_;
1985 $session_id = 0 if not defined $session_id;
1987 # Switch on or off?
1988 my $state= $st eq 'active' ? 'active': 'locked';
1990 my $ldap_handle = &get_ldap_handle($session_id);
1991 if( defined($ldap_handle) ) {
1993 # Build search filter for hosts
1994 my $search= "(&(objectClass=GOhard)";
1995 foreach (@{$targets}){
1996 $search.= "(macAddress=$_)";
1997 }
1998 $search.= ")";
2000 # If there's any host inside of the search string, procress them
2001 if (!($search =~ /macAddress/)){
2002 return;
2003 }
2005 # Perform search for Unit Tag
2006 my $mesg = $ldap_handle->search(
2007 base => $ldap_base,
2008 scope => 'sub',
2009 attrs => ['dn', 'gotoMode'],
2010 filter => "$search"
2011 );
2013 if ($mesg->count) {
2014 my @entries = $mesg->entries;
2015 foreach my $entry (@entries) {
2017 # Only modify entry if it is not set to '$state'
2018 if ($entry->get_value("gotoMode") ne $state){
2020 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2021 my $result;
2022 $result= $ldap_handle->modify($entry->dn, changes => [
2023 replace => [ gotoMode => $state ] ]);
2025 # Errors?
2026 if ($result->code){
2027 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2028 }
2030 }
2031 }
2032 } else {
2033 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2034 }
2036 }
2037 }
2040 sub run_recreate_packages_db {
2041 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2042 my $session_id = $session->ID;
2043 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2044 $kernel->yield('create_fai_release_db', $fai_release_tn);
2045 $kernel->yield('create_fai_server_db', $fai_server_tn);
2046 return;
2047 }
2050 sub run_create_fai_server_db {
2051 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2052 my $session_id = $session->ID;
2053 my $task = POE::Wheel::Run->new(
2054 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2055 StdoutEvent => "session_run_result",
2056 StderrEvent => "session_run_debug",
2057 CloseEvent => "session_run_done",
2058 );
2060 $heap->{task}->{ $task->ID } = $task;
2061 return;
2062 }
2065 sub create_fai_server_db {
2066 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2067 my $result;
2069 if (not defined $session_id) { $session_id = 0; }
2070 my $ldap_handle = &get_ldap_handle();
2071 if(defined($ldap_handle)) {
2072 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2073 my $mesg= $ldap_handle->search(
2074 base => $ldap_base,
2075 scope => 'sub',
2076 attrs => ['FAIrepository', 'gosaUnitTag'],
2077 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2078 );
2079 if($mesg->{'resultCode'} == 0 &&
2080 $mesg->count != 0) {
2081 foreach my $entry (@{$mesg->{entries}}) {
2082 if($entry->exists('FAIrepository')) {
2083 # Add an entry for each Repository configured for server
2084 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2085 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2086 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2087 $result= $fai_server_db->add_dbentry( {
2088 table => $table_name,
2089 primkey => ['server', 'release', 'tag'],
2090 server => $tmp_url,
2091 release => $tmp_release,
2092 sections => $tmp_sections,
2093 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2094 } );
2095 }
2096 }
2097 }
2098 }
2099 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2101 # TODO: Find a way to post the 'create_packages_list_db' event
2102 if(not defined($dont_create_packages_list)) {
2103 &create_packages_list_db(undef, undef, $session_id);
2104 }
2105 }
2107 $ldap_handle->disconnect;
2108 return $result;
2109 }
2112 sub run_create_fai_release_db {
2113 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2114 my $session_id = $session->ID;
2115 my $task = POE::Wheel::Run->new(
2116 Program => sub { &create_fai_release_db($table_name, $session_id) },
2117 StdoutEvent => "session_run_result",
2118 StderrEvent => "session_run_debug",
2119 CloseEvent => "session_run_done",
2120 );
2122 $heap->{task}->{ $task->ID } = $task;
2123 return;
2124 }
2127 sub create_fai_release_db {
2128 my ($table_name, $session_id) = @_;
2129 my $result;
2131 # used for logging
2132 if (not defined $session_id) { $session_id = 0; }
2134 my $ldap_handle = &get_ldap_handle();
2135 if(defined($ldap_handle)) {
2136 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2137 my $mesg= $ldap_handle->search(
2138 base => $ldap_base,
2139 scope => 'sub',
2140 attrs => [],
2141 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2142 );
2143 if($mesg->{'resultCode'} == 0 &&
2144 $mesg->count != 0) {
2145 # Walk through all possible FAI container ou's
2146 my @sql_list;
2147 my $timestamp= &get_time();
2148 foreach my $ou (@{$mesg->{entries}}) {
2149 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2150 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2151 my @tmp_array=get_fai_release_entries($tmp_classes);
2152 if(@tmp_array) {
2153 foreach my $entry (@tmp_array) {
2154 if(defined($entry) && ref($entry) eq 'HASH') {
2155 my $sql=
2156 "INSERT INTO $table_name "
2157 ."(timestamp, release, class, type, state) VALUES ("
2158 .$timestamp.","
2159 ."'".$entry->{'release'}."',"
2160 ."'".$entry->{'class'}."',"
2161 ."'".$entry->{'type'}."',"
2162 ."'".$entry->{'state'}."')";
2163 push @sql_list, $sql;
2164 }
2165 }
2166 }
2167 }
2168 }
2170 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2171 if(@sql_list) {
2172 unshift @sql_list, "VACUUM";
2173 unshift @sql_list, "DELETE FROM $table_name";
2174 $fai_release_db->exec_statementlist(\@sql_list);
2175 }
2176 daemon_log("$session_id DEBUG: Done with inserting",7);
2177 }
2178 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2179 }
2180 $ldap_handle->disconnect;
2181 return $result;
2182 }
2184 sub get_fai_types {
2185 my $tmp_classes = shift || return undef;
2186 my @result;
2188 foreach my $type(keys %{$tmp_classes}) {
2189 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2190 my $entry = {
2191 type => $type,
2192 state => $tmp_classes->{$type}[0],
2193 };
2194 push @result, $entry;
2195 }
2196 }
2198 return @result;
2199 }
2201 sub get_fai_state {
2202 my $result = "";
2203 my $tmp_classes = shift || return $result;
2205 foreach my $type(keys %{$tmp_classes}) {
2206 if(defined($tmp_classes->{$type}[0])) {
2207 $result = $tmp_classes->{$type}[0];
2209 # State is equal for all types in class
2210 last;
2211 }
2212 }
2214 return $result;
2215 }
2217 sub resolve_fai_classes {
2218 my ($fai_base, $ldap_handle, $session_id) = @_;
2219 if (not defined $session_id) { $session_id = 0; }
2220 my $result;
2221 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2222 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2223 my $fai_classes;
2225 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2226 my $mesg= $ldap_handle->search(
2227 base => $fai_base,
2228 scope => 'sub',
2229 attrs => ['cn','objectClass','FAIstate'],
2230 filter => $fai_filter,
2231 );
2232 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2234 if($mesg->{'resultCode'} == 0 &&
2235 $mesg->count != 0) {
2236 foreach my $entry (@{$mesg->{entries}}) {
2237 if($entry->exists('cn')) {
2238 my $tmp_dn= $entry->dn();
2240 # Skip classname and ou dn parts for class
2241 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2243 # Skip classes without releases
2244 if((!defined($tmp_release)) || length($tmp_release)==0) {
2245 next;
2246 }
2248 my $tmp_cn= $entry->get_value('cn');
2249 my $tmp_state= $entry->get_value('FAIstate');
2251 my $tmp_type;
2252 # Get FAI type
2253 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2254 if(grep $_ eq $oclass, @possible_fai_classes) {
2255 $tmp_type= $oclass;
2256 last;
2257 }
2258 }
2260 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2261 # A Subrelease
2262 my @sub_releases = split(/,/, $tmp_release);
2264 # Walk through subreleases and build hash tree
2265 my $hash;
2266 while(my $tmp_sub_release = pop @sub_releases) {
2267 $hash .= "\{'$tmp_sub_release'\}->";
2268 }
2269 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2270 } else {
2271 # A branch, no subrelease
2272 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2273 }
2274 } elsif (!$entry->exists('cn')) {
2275 my $tmp_dn= $entry->dn();
2276 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2278 # Skip classes without releases
2279 if((!defined($tmp_release)) || length($tmp_release)==0) {
2280 next;
2281 }
2283 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2284 # A Subrelease
2285 my @sub_releases= split(/,/, $tmp_release);
2287 # Walk through subreleases and build hash tree
2288 my $hash;
2289 while(my $tmp_sub_release = pop @sub_releases) {
2290 $hash .= "\{'$tmp_sub_release'\}->";
2291 }
2292 # Remove the last two characters
2293 chop($hash);
2294 chop($hash);
2296 eval('$fai_classes->'.$hash.'= {}');
2297 } else {
2298 # A branch, no subrelease
2299 if(!exists($fai_classes->{$tmp_release})) {
2300 $fai_classes->{$tmp_release} = {};
2301 }
2302 }
2303 }
2304 }
2306 # The hash is complete, now we can honor the copy-on-write based missing entries
2307 foreach my $release (keys %$fai_classes) {
2308 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2309 }
2310 }
2311 return $result;
2312 }
2314 sub apply_fai_inheritance {
2315 my $fai_classes = shift || return {};
2316 my $tmp_classes;
2318 # Get the classes from the branch
2319 foreach my $class (keys %{$fai_classes}) {
2320 # Skip subreleases
2321 if($class =~ /^ou=.*$/) {
2322 next;
2323 } else {
2324 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2325 }
2326 }
2328 # Apply to each subrelease
2329 foreach my $subrelease (keys %{$fai_classes}) {
2330 if($subrelease =~ /ou=/) {
2331 foreach my $tmp_class (keys %{$tmp_classes}) {
2332 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2333 $fai_classes->{$subrelease}->{$tmp_class} =
2334 deep_copy($tmp_classes->{$tmp_class});
2335 } else {
2336 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2337 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2338 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2339 deep_copy($tmp_classes->{$tmp_class}->{$type});
2340 }
2341 }
2342 }
2343 }
2344 }
2345 }
2347 # Find subreleases in deeper levels
2348 foreach my $subrelease (keys %{$fai_classes}) {
2349 if($subrelease =~ /ou=/) {
2350 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2351 if($subsubrelease =~ /ou=/) {
2352 apply_fai_inheritance($fai_classes->{$subrelease});
2353 }
2354 }
2355 }
2356 }
2358 return $fai_classes;
2359 }
2361 sub get_fai_release_entries {
2362 my $tmp_classes = shift || return;
2363 my $parent = shift || "";
2364 my @result = shift || ();
2366 foreach my $entry (keys %{$tmp_classes}) {
2367 if(defined($entry)) {
2368 if($entry =~ /^ou=.*$/) {
2369 my $release_name = $entry;
2370 $release_name =~ s/ou=//g;
2371 if(length($parent)>0) {
2372 $release_name = $parent."/".$release_name;
2373 }
2374 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2375 foreach my $bufentry(@bufentries) {
2376 push @result, $bufentry;
2377 }
2378 } else {
2379 my @types = get_fai_types($tmp_classes->{$entry});
2380 foreach my $type (@types) {
2381 push @result,
2382 {
2383 'class' => $entry,
2384 'type' => $type->{'type'},
2385 'release' => $parent,
2386 'state' => $type->{'state'},
2387 };
2388 }
2389 }
2390 }
2391 }
2393 return @result;
2394 }
2396 sub deep_copy {
2397 my $this = shift;
2398 if (not ref $this) {
2399 $this;
2400 } elsif (ref $this eq "ARRAY") {
2401 [map deep_copy($_), @$this];
2402 } elsif (ref $this eq "HASH") {
2403 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2404 } else { die "what type is $_?" }
2405 }
2408 sub session_run_result {
2409 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2410 $kernel->sig(CHLD => "child_reap");
2411 }
2413 sub session_run_debug {
2414 my $result = $_[ARG0];
2415 print STDERR "$result\n";
2416 }
2418 sub session_run_done {
2419 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2420 delete $heap->{task}->{$task_id};
2421 }
2424 sub create_sources_list {
2425 my $session_id = shift;
2426 my $ldap_handle = &main::get_ldap_handle;
2427 my $result="/tmp/gosa_si_tmp_sources_list";
2429 # Remove old file
2430 if(stat($result)) {
2431 unlink($result);
2432 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2433 }
2435 my $fh;
2436 open($fh, ">$result");
2437 if (not defined $fh) {
2438 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2439 return undef;
2440 }
2441 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2442 my $mesg=$ldap_handle->search(
2443 base => $main::ldap_server_dn,
2444 scope => 'base',
2445 attrs => 'FAIrepository',
2446 filter => 'objectClass=FAIrepositoryServer'
2447 );
2448 if($mesg->count) {
2449 foreach my $entry(@{$mesg->{'entries'}}) {
2450 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2451 my ($server, $tag, $release, $sections)= split /\|/, $value;
2452 my $line = "deb $server $release";
2453 $sections =~ s/,/ /g;
2454 $line.= " $sections";
2455 print $fh $line."\n";
2456 }
2457 }
2458 }
2459 } else {
2460 if (defined $main::ldap_server_dn){
2461 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2462 } else {
2463 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2464 }
2465 }
2466 close($fh);
2468 return $result;
2469 }
2472 sub run_create_packages_list_db {
2473 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2474 my $session_id = $session->ID;
2476 my $task = POE::Wheel::Run->new(
2477 Priority => +20,
2478 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2479 StdoutEvent => "session_run_result",
2480 StderrEvent => "session_run_debug",
2481 CloseEvent => "session_run_done",
2482 );
2483 $heap->{task}->{ $task->ID } = $task;
2484 }
2487 sub create_packages_list_db {
2488 my ($ldap_handle, $sources_file, $session_id) = @_;
2490 # it should not be possible to trigger a recreation of packages_list_db
2491 # while packages_list_db is under construction, so set flag packages_list_under_construction
2492 # which is tested befor recreation can be started
2493 if (-r $packages_list_under_construction) {
2494 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2495 return;
2496 } else {
2497 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2498 # set packages_list_under_construction to true
2499 system("touch $packages_list_under_construction");
2500 @packages_list_statements=();
2501 }
2503 if (not defined $session_id) { $session_id = 0; }
2504 if (not defined $ldap_handle) {
2505 $ldap_handle= &get_ldap_handle();
2507 if (not defined $ldap_handle) {
2508 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2509 unlink($packages_list_under_construction);
2510 return;
2511 }
2512 }
2513 if (not defined $sources_file) {
2514 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2515 $sources_file = &create_sources_list($session_id);
2516 }
2518 if (not defined $sources_file) {
2519 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2520 unlink($packages_list_under_construction);
2521 return;
2522 }
2524 my $line;
2526 open(CONFIG, "<$sources_file") or do {
2527 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2528 unlink($packages_list_under_construction);
2529 return;
2530 };
2532 # Read lines
2533 while ($line = <CONFIG>){
2534 # Unify
2535 chop($line);
2536 $line =~ s/^\s+//;
2537 $line =~ s/^\s+/ /;
2539 # Strip comments
2540 $line =~ s/#.*$//g;
2542 # Skip empty lines
2543 if ($line =~ /^\s*$/){
2544 next;
2545 }
2547 # Interpret deb line
2548 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2549 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2550 my $section;
2551 foreach $section (split(' ', $sections)){
2552 &parse_package_info( $baseurl, $dist, $section, $session_id );
2553 }
2554 }
2555 }
2557 close (CONFIG);
2559 find(\&cleanup_and_extract, keys( %repo_dirs ));
2560 &main::strip_packages_list_statements();
2561 unshift @packages_list_statements, "VACUUM";
2562 $packages_list_db->exec_statementlist(\@packages_list_statements);
2563 unlink($packages_list_under_construction);
2564 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2565 return;
2566 }
2568 # This function should do some intensive task to minimize the db-traffic
2569 sub strip_packages_list_statements {
2570 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2571 my @new_statement_list=();
2572 my $hash;
2573 my $insert_hash;
2574 my $update_hash;
2575 my $delete_hash;
2576 my $local_timestamp=get_time();
2578 foreach my $existing_entry (@existing_entries) {
2579 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2580 }
2582 foreach my $statement (@packages_list_statements) {
2583 if($statement =~ /^INSERT/i) {
2584 # Assign the values from the insert statement
2585 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2586 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2587 if(exists($hash->{$distribution}->{$package}->{$version})) {
2588 # If section or description has changed, update the DB
2589 if(
2590 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2591 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2592 ) {
2593 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2594 }
2595 } else {
2596 # Insert a non-existing entry to db
2597 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2598 }
2599 } elsif ($statement =~ /^UPDATE/i) {
2600 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2601 /^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;
2602 foreach my $distribution (keys %{$hash}) {
2603 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2604 # update the insertion hash to execute only one query per package (insert instead insert+update)
2605 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2606 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2607 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2608 my $section;
2609 my $description;
2610 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2611 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2612 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2613 }
2614 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2615 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2616 }
2617 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2618 }
2619 }
2620 }
2621 }
2622 }
2624 # TODO: Check for orphaned entries
2626 # unroll the insert_hash
2627 foreach my $distribution (keys %{$insert_hash}) {
2628 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2629 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2630 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2631 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2632 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2633 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2634 ."'$local_timestamp')";
2635 }
2636 }
2637 }
2639 # unroll the update hash
2640 foreach my $distribution (keys %{$update_hash}) {
2641 foreach my $package (keys %{$update_hash->{$distribution}}) {
2642 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2643 my $set = "";
2644 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2645 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2646 }
2647 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2648 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2649 }
2650 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2651 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2652 }
2653 if(defined($set) and length($set) > 0) {
2654 $set .= "timestamp = '$local_timestamp'";
2655 } else {
2656 next;
2657 }
2658 push @new_statement_list,
2659 "UPDATE $main::packages_list_tn SET $set WHERE"
2660 ." distribution = '$distribution'"
2661 ." AND package = '$package'"
2662 ." AND version = '$version'";
2663 }
2664 }
2665 }
2667 @packages_list_statements = @new_statement_list;
2668 }
2671 sub parse_package_info {
2672 my ($baseurl, $dist, $section, $session_id)= @_;
2673 my ($package);
2674 if (not defined $session_id) { $session_id = 0; }
2675 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2676 $repo_dirs{ "${repo_path}/pool" } = 1;
2678 foreach $package ("Packages.gz"){
2679 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2680 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2681 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2682 }
2684 }
2687 sub get_package {
2688 my ($url, $dest, $session_id)= @_;
2689 if (not defined $session_id) { $session_id = 0; }
2691 my $tpath = dirname($dest);
2692 -d "$tpath" || mkpath "$tpath";
2694 # This is ugly, but I've no time to take a look at "how it works in perl"
2695 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2696 system("gunzip -cd '$dest' > '$dest.in'");
2697 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2698 unlink($dest);
2699 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2700 } else {
2701 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2702 }
2703 return 0;
2704 }
2707 sub parse_package {
2708 my ($path, $dist, $srv_path, $session_id)= @_;
2709 if (not defined $session_id) { $session_id = 0;}
2710 my ($package, $version, $section, $description);
2711 my $PACKAGES;
2712 my $timestamp = &get_time();
2714 if(not stat("$path.in")) {
2715 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2716 return;
2717 }
2719 open($PACKAGES, "<$path.in");
2720 if(not defined($PACKAGES)) {
2721 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2722 return;
2723 }
2725 # Read lines
2726 while (<$PACKAGES>){
2727 my $line = $_;
2728 # Unify
2729 chop($line);
2731 # Use empty lines as a trigger
2732 if ($line =~ /^\s*$/){
2733 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2734 push(@packages_list_statements, $sql);
2735 $package = "none";
2736 $version = "none";
2737 $section = "none";
2738 $description = "none";
2739 next;
2740 }
2742 # Trigger for package name
2743 if ($line =~ /^Package:\s/){
2744 ($package)= ($line =~ /^Package: (.*)$/);
2745 next;
2746 }
2748 # Trigger for version
2749 if ($line =~ /^Version:\s/){
2750 ($version)= ($line =~ /^Version: (.*)$/);
2751 next;
2752 }
2754 # Trigger for description
2755 if ($line =~ /^Description:\s/){
2756 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2757 next;
2758 }
2760 # Trigger for section
2761 if ($line =~ /^Section:\s/){
2762 ($section)= ($line =~ /^Section: (.*)$/);
2763 next;
2764 }
2766 # Trigger for filename
2767 if ($line =~ /^Filename:\s/){
2768 my ($filename) = ($line =~ /^Filename: (.*)$/);
2769 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2770 next;
2771 }
2772 }
2774 close( $PACKAGES );
2775 unlink( "$path.in" );
2776 }
2779 sub store_fileinfo {
2780 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2782 my %fileinfo = (
2783 'package' => $package,
2784 'dist' => $dist,
2785 'version' => $vers,
2786 );
2788 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2789 }
2792 sub cleanup_and_extract {
2793 my $fileinfo = $repo_files{ $File::Find::name };
2795 if( defined $fileinfo ) {
2797 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2798 my $sql;
2799 my $package = $fileinfo->{ 'package' };
2800 my $newver = $fileinfo->{ 'version' };
2802 mkpath($dir);
2803 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2805 if( -f "$dir/DEBIAN/templates" ) {
2807 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2809 my $tmpl= "";
2810 {
2811 local $/=undef;
2812 open FILE, "$dir/DEBIAN/templates";
2813 $tmpl = &encode_base64(<FILE>);
2814 close FILE;
2815 }
2816 rmtree("$dir/DEBIAN/templates");
2818 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2819 push @packages_list_statements, $sql;
2820 }
2821 }
2823 return;
2824 }
2827 sub register_at_foreign_servers {
2828 my ($kernel) = $_[KERNEL];
2830 # hole alle bekannten server aus known_server_db
2831 my $server_sql = "SELECT * FROM $known_server_tn";
2832 my $server_res = $known_server_db->exec_statement($server_sql);
2834 # no entries in known_server_db
2835 if (not ref(@$server_res[0]) eq "ARRAY") {
2836 # TODO
2837 }
2839 # detect already connected clients
2840 my $client_sql = "SELECT * FROM $known_clients_tn";
2841 my $client_res = $known_clients_db->exec_statement($client_sql);
2843 # send my server details to all other gosa-si-server within the network
2844 foreach my $hit (@$server_res) {
2845 my $hostname = @$hit[0];
2846 my $hostkey = &create_passwd;
2848 # add already connected clients to registration message
2849 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2850 &add_content2xml_hash($myhash, 'key', $hostkey);
2851 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2853 # build registration message and send it
2854 my $foreign_server_msg = &create_xml_string($myhash);
2855 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2856 }
2858 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2859 return;
2860 }
2863 #==== MAIN = main ==============================================================
2864 # parse commandline options
2865 Getopt::Long::Configure( "bundling" );
2866 GetOptions("h|help" => \&usage,
2867 "c|config=s" => \$cfg_file,
2868 "f|foreground" => \$foreground,
2869 "v|verbose+" => \$verbose,
2870 "no-arp+" => \$no_arp,
2871 );
2873 # read and set config parameters
2874 &check_cmdline_param ;
2875 &read_configfile;
2876 &check_pid;
2878 $SIG{CHLD} = 'IGNORE';
2880 # forward error messages to logfile
2881 if( ! $foreground ) {
2882 open( STDIN, '+>/dev/null' );
2883 open( STDOUT, '+>&STDIN' );
2884 open( STDERR, '+>&STDIN' );
2885 }
2887 # Just fork, if we are not in foreground mode
2888 if( ! $foreground ) {
2889 chdir '/' or die "Can't chdir to /: $!";
2890 $pid = fork;
2891 setsid or die "Can't start a new session: $!";
2892 umask 0;
2893 } else {
2894 $pid = $$;
2895 }
2897 # Do something useful - put our PID into the pid_file
2898 if( 0 != $pid ) {
2899 open( LOCK_FILE, ">$pid_file" );
2900 print LOCK_FILE "$pid\n";
2901 close( LOCK_FILE );
2902 if( !$foreground ) {
2903 exit( 0 )
2904 };
2905 }
2907 # parse head url and revision from svn
2908 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2909 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2910 $server_headURL = defined $1 ? $1 : 'unknown' ;
2911 $server_revision = defined $2 ? $2 : 'unknown' ;
2912 if ($server_headURL =~ /\/tag\// ||
2913 $server_headURL =~ /\/branches\// ) {
2914 $server_status = "stable";
2915 } else {
2916 $server_status = "developmental" ;
2917 }
2920 daemon_log(" ", 1);
2921 daemon_log("$0 started!", 1);
2922 daemon_log("status: $server_status", 1);
2923 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2925 # connect to incoming_db
2926 unlink($incoming_file_name);
2927 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2928 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2930 # connect to gosa-si job queue
2931 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2932 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2934 # connect to known_clients_db
2935 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2936 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2938 # connect to foreign_clients_db
2939 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2940 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2942 # connect to known_server_db
2943 unlink($known_server_file_name);
2944 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2945 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2947 # connect to login_usr_db
2948 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2949 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2951 # connect to fai_server_db and fai_release_db
2952 unlink($fai_server_file_name);
2953 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2954 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2956 unlink($fai_release_file_name);
2957 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2958 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2960 # connect to packages_list_db
2961 #unlink($packages_list_file_name);
2962 unlink($packages_list_under_construction);
2963 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2964 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2966 # connect to messaging_db
2967 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2968 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2971 # create xml object used for en/decrypting
2972 $xml = new XML::Simple();
2975 # foreign servers
2976 my @foreign_server_list;
2978 # add foreign server from cfg file
2979 if ($foreign_server_string ne "") {
2980 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2981 foreach my $foreign_server (@cfg_foreign_server_list) {
2982 push(@foreign_server_list, $foreign_server);
2983 }
2984 }
2986 # add foreign server from dns
2987 my @tmp_servers;
2988 if ( !$server_domain) {
2989 # Try our DNS Searchlist
2990 for my $domain(get_dns_domains()) {
2991 chomp($domain);
2992 my @tmp_domains= &get_server_addresses($domain);
2993 if(@tmp_domains) {
2994 for my $tmp_server(@tmp_domains) {
2995 push @tmp_servers, $tmp_server;
2996 }
2997 }
2998 }
2999 if(@tmp_servers && length(@tmp_servers)==0) {
3000 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3001 }
3002 } else {
3003 @tmp_servers = &get_server_addresses($server_domain);
3004 if( 0 == @tmp_servers ) {
3005 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3006 }
3007 }
3008 foreach my $server (@tmp_servers) {
3009 unshift(@foreign_server_list, $server);
3010 }
3011 # eliminate duplicate entries
3012 @foreign_server_list = &del_doubles(@foreign_server_list);
3013 my $all_foreign_server = join(", ", @foreign_server_list);
3014 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3016 # add all found foreign servers to known_server
3017 my $act_timestamp = &get_time();
3018 foreach my $foreign_server (@foreign_server_list) {
3020 # do not add myself to known_server_db
3021 if (&is_local($foreign_server)) { next; }
3022 ######################################
3024 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3025 primkey=>['hostname'],
3026 hostname=>$foreign_server,
3027 status=>'not_jet_registered',
3028 hostkey=>"none",
3029 timestamp=>$act_timestamp,
3030 } );
3031 }
3034 # import all modules
3035 &import_modules;
3036 # check wether all modules are gosa-si valid passwd check
3037 &password_check;
3040 POE::Component::Server::TCP->new(
3041 Alias => "TCP_SERVER",
3042 Port => $server_port,
3043 ClientInput => sub {
3044 my ($kernel, $input) = @_[KERNEL, ARG0];
3045 push(@tasks, $input);
3046 push(@msgs_to_decrypt, $input);
3047 $kernel->yield("msg_to_decrypt");
3048 },
3049 InlineStates => {
3050 msg_to_decrypt => \&msg_to_decrypt,
3051 next_task => \&next_task,
3052 task_result => \&handle_task_result,
3053 task_done => \&handle_task_done,
3054 task_debug => \&handle_task_debug,
3055 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3056 }
3057 );
3059 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3061 # create session for repeatedly checking the job queue for jobs
3062 POE::Session->create(
3063 inline_states => {
3064 _start => \&session_start,
3065 register_at_foreign_servers => \®ister_at_foreign_servers,
3066 sig_handler => \&sig_handler,
3067 next_task => \&next_task,
3068 task_result => \&handle_task_result,
3069 task_done => \&handle_task_done,
3070 task_debug => \&handle_task_debug,
3071 watch_for_next_tasks => \&watch_for_next_tasks,
3072 watch_for_new_messages => \&watch_for_new_messages,
3073 watch_for_delivery_messages => \&watch_for_delivery_messages,
3074 watch_for_done_messages => \&watch_for_done_messages,
3075 watch_for_new_jobs => \&watch_for_new_jobs,
3076 watch_for_modified_jobs => \&watch_for_modified_jobs,
3077 watch_for_done_jobs => \&watch_for_done_jobs,
3078 watch_for_old_known_clients => \&watch_for_old_known_clients,
3079 create_packages_list_db => \&run_create_packages_list_db,
3080 create_fai_server_db => \&run_create_fai_server_db,
3081 create_fai_release_db => \&run_create_fai_release_db,
3082 recreate_packages_db => \&run_recreate_packages_db,
3083 session_run_result => \&session_run_result,
3084 session_run_debug => \&session_run_debug,
3085 session_run_done => \&session_run_done,
3086 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3087 }
3088 );
3091 POE::Kernel->run();
3092 exit;