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