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