c1eb86f3b4194f2bfa2dd634bd42e8793b437ea3
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);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
81 # variables declared in config file are always set to 'our'
82 our (%cfg_defaults, $log_file, $pid_file,
83 $server_ip, $server_port, $ClientPackages_key,
84 $arp_activ, $gosa_unit_tag,
85 $GosaPackages_key, $gosa_timeout,
86 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
87 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
88 $arp_enabled, $arp_interface,
89 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
90 $new_systems_ou,
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);
102 our $known_modules;
104 # specifies the verbosity of the daemon_log
105 $verbose = 0 ;
107 # if foreground is not null, script will be not forked to background
108 $foreground = 0 ;
110 # specifies the timeout seconds while checking the online status of a registrating client
111 $ping_timeout = 5;
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
123 "timestamp DEFAULT 'none'",
124 "headertag DEFAULT 'none'",
125 "targettag DEFAULT 'none'",
126 "xmlmessage DEFAULT 'none'",
127 "module DEFAULT 'none'",
128 "sessionid DEFAULT '0'",
129 );
131 # holds all gosa jobs
132 our $job_db;
133 our $job_queue_tn = 'jobs';
134 my $job_queue_file_name;
135 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
136 "timestamp DEFAULT 'none'",
137 "status DEFAULT 'none'",
138 "result DEFAULT 'none'",
139 "progress DEFAULT 'none'",
140 "headertag DEFAULT 'none'",
141 "targettag DEFAULT 'none'",
142 "xmlmessage DEFAULT 'none'",
143 "macaddress DEFAULT 'none'",
144 "plainname DEFAULT 'none'",
145 "siserver DEFAULT 'none'",
146 "modified DEFAULT '0'",
147 );
149 # holds all other gosa-si-server
150 our $known_server_db;
151 our $known_server_tn = "known_server";
152 my $known_server_file_name;
153 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
155 # holds all registrated clients
156 our $known_clients_db;
157 our $known_clients_tn = "known_clients";
158 my $known_clients_file_name;
159 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
161 # holds all registered clients at a foreign server
162 our $foreign_clients_db;
163 our $foreign_clients_tn = "foreign_clients";
164 my $foreign_clients_file_name;
165 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
167 # holds all logged in user at each client
168 our $login_users_db;
169 our $login_users_tn = "login_users";
170 my $login_users_file_name;
171 my @login_users_col_names = ("client", "user", "timestamp");
173 # holds all fai server, the debian release and tag
174 our $fai_server_db;
175 our $fai_server_tn = "fai_server";
176 my $fai_server_file_name;
177 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
179 our $fai_release_db;
180 our $fai_release_tn = "fai_release";
181 my $fai_release_file_name;
182 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
184 # holds all packages available from different repositories
185 our $packages_list_db;
186 our $packages_list_tn = "packages_list";
187 my $packages_list_file_name;
188 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
189 my $outdir = "/tmp/packages_list_db";
190 my $arch = "i386";
192 # holds all messages which should be delivered to a user
193 our $messaging_db;
194 our $messaging_tn = "messaging";
195 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
196 "flag", "direction", "delivery_time", "message", "timestamp" );
197 my $messaging_file_name;
199 # path to directory to store client install log files
200 our $client_fai_log_dir = "/var/log/fai";
202 # queue which stores taskes until one of the $max_children children are ready to process the task
203 my @tasks = qw();
204 my @msgs_to_decrypt = qw();
205 my $max_children = 2;
208 # loop delay for job queue to look for opsi jobs
209 my $job_queue_opsi_delay = 10;
210 our $opsi_client;
211 our $opsi_url;
213 # Lifetime of logged in user information. If no update information comes after n seconds,
214 # the user is expeceted to be no longer logged in or the host is no longer running. Because
215 # of this, the user is deleted from login_users_db
216 our $logged_in_user_date_of_expiry = 600;
219 %cfg_defaults = (
220 "general" => {
221 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
222 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
223 },
224 "server" => {
225 "ip" => [\$server_ip, "0.0.0.0"],
226 "port" => [\$server_port, "20081"],
227 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
228 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
229 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
230 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
231 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
232 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
233 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
234 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
235 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
236 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
237 "repo-path" => [\$repo_path, '/srv/www/repository'],
238 "ldap-uri" => [\$ldap_uri, ""],
239 "ldap-base" => [\$ldap_base, ""],
240 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
241 "ldap-admin-password" => [\$ldap_admin_password, ""],
242 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
243 "max-clients" => [\$max_clients, 10],
244 "wol-password" => [\$wake_on_lan_passwd, ""],
245 },
246 "GOsaPackages" => {
247 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
248 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
249 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
250 "key" => [\$GosaPackages_key, "none"],
251 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
252 },
253 "ClientPackages" => {
254 "key" => [\$ClientPackages_key, "none"],
255 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
256 },
257 "ServerPackages"=> {
258 "address" => [\$foreign_server_string, ""],
259 "domain" => [\$server_domain, ""],
260 "key" => [\$ServerPackages_key, "none"],
261 "key-lifetime" => [\$foreign_servers_register_delay, 120],
262 "job-synchronization-enabled" => [\$job_synchronization, "true"],
263 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
264 },
265 "ArpHandler" => {
266 "enabled" => [\$arp_enabled, "true"],
267 "interface" => [\$arp_interface, "all"],
268 },
269 "Opsi" => {
270 "enabled" => [\$opsi_enabled, "false"],
271 "server" => [\$opsi_server, "localhost"],
272 "admin" => [\$opsi_admin, "opsi-admin"],
273 "password" => [\$opsi_password, "secret"],
274 },
276 );
279 #=== FUNCTION ================================================================
280 # NAME: usage
281 # PARAMETERS: nothing
282 # RETURNS: nothing
283 # DESCRIPTION: print out usage text to STDERR
284 #===============================================================================
285 sub usage {
286 print STDERR << "EOF" ;
287 usage: $prg [-hvf] [-c config]
289 -h : this (help) message
290 -c <file> : config file
291 -f : foreground, process will not be forked to background
292 -v : be verbose (multiple to increase verbosity)
293 -no-arp : starts $prg without connection to arp module
295 EOF
296 print "\n" ;
297 }
300 #=== FUNCTION ================================================================
301 # NAME: logging
302 # PARAMETERS: level - string - default 'info'
303 # msg - string -
304 # facility - string - default 'LOG_DAEMON'
305 # RETURNS: nothing
306 # DESCRIPTION: function for logging
307 #===============================================================================
308 sub daemon_log {
309 # log into log_file
310 my( $msg, $level ) = @_;
311 if(not defined $msg) { return }
312 if(not defined $level) { $level = 1 }
313 if(defined $log_file){
314 open(LOG_HANDLE, ">>$log_file");
315 chmod 0600, $log_file;
316 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
317 print STDERR "cannot open $log_file: $!";
318 return
319 }
320 chomp($msg);
321 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
322 if($level <= $verbose){
323 my ($seconds, $minutes, $hours, $monthday, $month,
324 $year, $weekday, $yearday, $sommertime) = localtime(time);
325 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
326 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
327 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
328 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
329 $month = $monthnames[$month];
330 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
331 $year+=1900;
332 my $name = $prg;
334 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
335 print LOG_HANDLE $log_msg;
336 if( $foreground ) {
337 print STDERR $log_msg;
338 }
339 }
340 close( LOG_HANDLE );
341 }
342 }
345 #=== FUNCTION ================================================================
346 # NAME: check_cmdline_param
347 # PARAMETERS: nothing
348 # RETURNS: nothing
349 # DESCRIPTION: validates commandline parameter
350 #===============================================================================
351 sub check_cmdline_param () {
352 my $err_config;
353 my $err_counter = 0;
354 if(not defined($cfg_file)) {
355 $cfg_file = "/etc/gosa-si/server.conf";
356 if(! -r $cfg_file) {
357 $err_config = "please specify a config file";
358 $err_counter += 1;
359 }
360 }
361 if( $err_counter > 0 ) {
362 &usage( "", 1 );
363 if( defined( $err_config)) { print STDERR "$err_config\n"}
364 print STDERR "\n";
365 exit( -1 );
366 }
367 }
370 #=== FUNCTION ================================================================
371 # NAME: check_pid
372 # PARAMETERS: nothing
373 # RETURNS: nothing
374 # DESCRIPTION: handels pid processing
375 #===============================================================================
376 sub check_pid {
377 $pid = -1;
378 # Check, if we are already running
379 if( open(LOCK_FILE, "<$pid_file") ) {
380 $pid = <LOCK_FILE>;
381 if( defined $pid ) {
382 chomp( $pid );
383 if( -f "/proc/$pid/stat" ) {
384 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
385 if( $stat ) {
386 daemon_log("ERROR: Already running",1);
387 close( LOCK_FILE );
388 exit -1;
389 }
390 }
391 }
392 close( LOCK_FILE );
393 unlink( $pid_file );
394 }
396 # create a syslog msg if it is not to possible to open PID file
397 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
398 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
399 if (open(LOCK_FILE, '<', $pid_file)
400 && ($pid = <LOCK_FILE>))
401 {
402 chomp($pid);
403 $msg .= "(PID $pid)\n";
404 } else {
405 $msg .= "(unable to read PID)\n";
406 }
407 if( ! ($foreground) ) {
408 openlog( $0, "cons,pid", "daemon" );
409 syslog( "warning", $msg );
410 closelog();
411 }
412 else {
413 print( STDERR " $msg " );
414 }
415 exit( -1 );
416 }
417 }
419 #=== FUNCTION ================================================================
420 # NAME: import_modules
421 # PARAMETERS: module_path - string - abs. path to the directory the modules
422 # are stored
423 # RETURNS: nothing
424 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
425 # state is on is imported by "require 'file';"
426 #===============================================================================
427 sub import_modules {
428 daemon_log(" ", 1);
430 if (not -e $modules_path) {
431 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
432 }
434 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
435 while (defined (my $file = readdir (DIR))) {
436 if (not $file =~ /(\S*?).pm$/) {
437 next;
438 }
439 my $mod_name = $1;
441 # ArpHandler switch
442 if( $file =~ /ArpHandler.pm/ ) {
443 if( $arp_enabled eq "false" ) { next; }
444 }
446 eval { require $file; };
447 if ($@) {
448 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
449 daemon_log("$@", 5);
450 } else {
451 my $info = eval($mod_name.'::get_module_info()');
452 # Only load module if get_module_info() returns a non-null object
453 if( $info ) {
454 my ($input_address, $input_key, $event_hash) = @{$info};
455 $known_modules->{$mod_name} = $info;
456 daemon_log("0 INFO: module $mod_name loaded", 5);
457 }
458 }
459 }
461 close (DIR);
462 }
464 #=== FUNCTION ================================================================
465 # NAME: password_check
466 # PARAMETERS: nothing
467 # RETURNS: nothing
468 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
469 # the same password
470 #===============================================================================
471 sub password_check {
472 my $passwd_hash = {};
473 while (my ($mod_name, $mod_info) = each %$known_modules) {
474 my $mod_passwd = @$mod_info[1];
475 if (not defined $mod_passwd) { next; }
476 if (not exists $passwd_hash->{$mod_passwd}) {
477 $passwd_hash->{$mod_passwd} = $mod_name;
479 # escalates critical error
480 } else {
481 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
482 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
483 exit( -1 );
484 }
485 }
487 }
490 #=== FUNCTION ================================================================
491 # NAME: sig_int_handler
492 # PARAMETERS: signal - string - signal arose from system
493 # RETURNS: nothing
494 # DESCRIPTION: handels tasks to be done befor signal becomes active
495 #===============================================================================
496 sub sig_int_handler {
497 my ($signal) = @_;
499 # if (defined($ldap_handle)) {
500 # $ldap_handle->disconnect;
501 # }
502 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
505 daemon_log("shutting down gosa-si-server", 1);
506 system("kill `ps -C gosa-si-server -o pid=`");
507 }
508 $SIG{INT} = \&sig_int_handler;
511 sub check_key_and_xml_validity {
512 my ($crypted_msg, $module_key, $session_id) = @_;
513 my $msg;
514 my $msg_hash;
515 my $error_string;
516 eval{
517 $msg = &decrypt_msg($crypted_msg, $module_key);
519 if ($msg =~ /<xml>/i){
520 $msg =~ s/\s+/ /g; # just for better daemon_log
521 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
522 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
524 ##############
525 # check header
526 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
527 my $header_l = $msg_hash->{'header'};
528 if( 1 > @{$header_l} ) { die 'empty header tag'; }
529 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
530 my $header = @{$header_l}[0];
531 if( 0 == length $header) { die 'empty string in header tag'; }
533 ##############
534 # check source
535 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
536 my $source_l = $msg_hash->{'source'};
537 if( 1 > @{$source_l} ) { die 'empty source tag'; }
538 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
539 my $source = @{$source_l}[0];
540 if( 0 == length $source) { die 'source error'; }
542 ##############
543 # check target
544 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
545 my $target_l = $msg_hash->{'target'};
546 if( 1 > @{$target_l} ) { die 'empty target tag'; }
547 }
548 };
549 if($@) {
550 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
551 $msg = undef;
552 $msg_hash = undef;
553 }
555 return ($msg, $msg_hash);
556 }
559 sub check_outgoing_xml_validity {
560 my ($msg, $session_id) = @_;
562 my $msg_hash;
563 eval{
564 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
566 ##############
567 # check header
568 my $header_l = $msg_hash->{'header'};
569 if( 1 != @{$header_l} ) {
570 die 'no or more than one headers specified';
571 }
572 my $header = @{$header_l}[0];
573 if( 0 == length $header) {
574 die 'header has length 0';
575 }
577 ##############
578 # check source
579 my $source_l = $msg_hash->{'source'};
580 if( 1 != @{$source_l} ) {
581 die 'no or more than 1 sources specified';
582 }
583 my $source = @{$source_l}[0];
584 if( 0 == length $source) {
585 die 'source has length 0';
586 }
587 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
588 $source =~ /^GOSA$/i ) {
589 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
590 }
592 ##############
593 # check target
594 my $target_l = $msg_hash->{'target'};
595 if( 0 == @{$target_l} ) {
596 die "no targets specified";
597 }
598 foreach my $target (@$target_l) {
599 if( 0 == length $target) {
600 die "target has length 0";
601 }
602 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
603 $target =~ /^GOSA$/i ||
604 $target =~ /^\*$/ ||
605 $target =~ /KNOWN_SERVER/i ||
606 $target =~ /JOBDB/i ||
607 $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 ){
608 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
609 }
610 }
611 };
612 if($@) {
613 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
614 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
615 $msg_hash = undef;
616 }
618 return ($msg_hash);
619 }
622 sub input_from_known_server {
623 my ($input, $remote_ip, $session_id) = @_ ;
624 my ($msg, $msg_hash, $module);
626 my $sql_statement= "SELECT * FROM known_server";
627 my $query_res = $known_server_db->select_dbentry( $sql_statement );
629 while( my ($hit_num, $hit) = each %{ $query_res } ) {
630 my $host_name = $hit->{hostname};
631 if( not $host_name =~ "^$remote_ip") {
632 next;
633 }
634 my $host_key = $hit->{hostkey};
635 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
636 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
638 # check if module can open msg envelope with module key
639 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
640 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
641 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
642 daemon_log("$@", 8);
643 next;
644 }
645 else {
646 $msg = $tmp_msg;
647 $msg_hash = $tmp_msg_hash;
648 $module = "ServerPackages";
649 last;
650 }
651 }
653 if( (!$msg) || (!$msg_hash) || (!$module) ) {
654 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
655 }
657 return ($msg, $msg_hash, $module);
658 }
661 sub input_from_known_client {
662 my ($input, $remote_ip, $session_id) = @_ ;
663 my ($msg, $msg_hash, $module);
665 my $sql_statement= "SELECT * FROM known_clients";
666 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
667 while( my ($hit_num, $hit) = each %{ $query_res } ) {
668 my $host_name = $hit->{hostname};
669 if( not $host_name =~ /^$remote_ip:\d*$/) {
670 next;
671 }
672 my $host_key = $hit->{hostkey};
673 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
674 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
676 # check if module can open msg envelope with module key
677 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
679 if( (!$msg) || (!$msg_hash) ) {
680 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
681 &daemon_log("$@", 8);
682 next;
683 }
684 else {
685 $module = "ClientPackages";
686 last;
687 }
688 }
690 if( (!$msg) || (!$msg_hash) || (!$module) ) {
691 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
692 }
694 return ($msg, $msg_hash, $module);
695 }
698 sub input_from_unknown_host {
699 no strict "refs";
700 my ($input, $session_id) = @_ ;
701 my ($msg, $msg_hash, $module);
702 my $error_string;
704 my %act_modules = %$known_modules;
706 while( my ($mod, $info) = each(%act_modules)) {
708 # check a key exists for this module
709 my $module_key = ${$mod."_key"};
710 if( not defined $module_key ) {
711 if( $mod eq 'ArpHandler' ) {
712 next;
713 }
714 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
715 next;
716 }
717 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
719 # check if module can open msg envelope with module key
720 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
721 if( (not defined $msg) || (not defined $msg_hash) ) {
722 daemon_log("$session_id ERROR: no msg returned!", 2) if ((not defined $msg) || "" eq $msg);
723 daemon_log("$session_id ERROR: no msg_hash returned!", 2) if ((not defined $msg_hash) || "" eq $msg_hash);
724 next;
725 } else {
726 $module = $mod;
727 last;
728 }
729 }
731 if( (!$msg) || (!$msg_hash) || (!$module)) {
732 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
733 }
735 return ($msg, $msg_hash, $module);
736 }
739 sub create_ciphering {
740 my ($passwd) = @_;
741 if((!defined($passwd)) || length($passwd)==0) {
742 $passwd = "";
743 }
744 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
745 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
746 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
747 $my_cipher->set_iv($iv);
748 return $my_cipher;
749 }
752 sub encrypt_msg {
753 my ($msg, $key) = @_;
754 my $my_cipher = &create_ciphering($key);
755 my $len;
756 {
757 use bytes;
758 $len= 16-length($msg)%16;
759 }
760 $msg = "\0"x($len).$msg;
761 $msg = $my_cipher->encrypt($msg);
762 chomp($msg = &encode_base64($msg));
763 # there are no newlines allowed inside msg
764 $msg=~ s/\n//g;
765 return $msg;
766 }
769 sub decrypt_msg {
771 my ($msg, $key) = @_ ;
772 $msg = &decode_base64($msg);
773 my $my_cipher = &create_ciphering($key);
774 $msg = $my_cipher->decrypt($msg);
775 $msg =~ s/\0*//g;
776 return $msg;
777 }
780 sub get_encrypt_key {
781 my ($target) = @_ ;
782 my $encrypt_key;
783 my $error = 0;
785 # target can be in known_server
786 if( not defined $encrypt_key ) {
787 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
788 my $query_res = $known_server_db->select_dbentry( $sql_statement );
789 while( my ($hit_num, $hit) = each %{ $query_res } ) {
790 my $host_name = $hit->{hostname};
791 if( $host_name ne $target ) {
792 next;
793 }
794 $encrypt_key = $hit->{hostkey};
795 last;
796 }
797 }
799 # target can be in known_client
800 if( not defined $encrypt_key ) {
801 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
802 my $query_res = $known_clients_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 return $encrypt_key;
814 }
817 #=== FUNCTION ================================================================
818 # NAME: open_socket
819 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
820 # [PeerPort] string necessary if port not appended by PeerAddr
821 # RETURNS: socket IO::Socket::INET
822 # DESCRIPTION: open a socket to PeerAddr
823 #===============================================================================
824 sub open_socket {
825 my ($PeerAddr, $PeerPort) = @_ ;
826 if(defined($PeerPort)){
827 $PeerAddr = $PeerAddr.":".$PeerPort;
828 }
829 my $socket;
830 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
831 Porto => "tcp",
832 Type => SOCK_STREAM,
833 Timeout => 5,
834 );
835 if(not defined $socket) {
836 return;
837 }
838 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
839 return $socket;
840 }
843 #sub get_local_ip_for_remote_ip {
844 # my $remote_ip= shift;
845 # my $result="0.0.0.0";
846 #
847 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
848 # if($remote_ip eq "127.0.0.1") {
849 # $result = "127.0.0.1";
850 # } else {
851 # my $PROC_NET_ROUTE= ('/proc/net/route');
852 #
853 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
854 # or die "Could not open $PROC_NET_ROUTE";
855 #
856 # my @ifs = <PROC_NET_ROUTE>;
857 #
858 # close(PROC_NET_ROUTE);
859 #
860 # # Eat header line
861 # shift @ifs;
862 # chomp @ifs;
863 # foreach my $line(@ifs) {
864 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
865 # my $destination;
866 # my $mask;
867 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
868 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
869 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
870 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
871 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
872 # # destination matches route, save mac and exit
873 # $result= &get_ip($Iface);
874 # last;
875 # }
876 # }
877 # }
878 # } else {
879 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
880 # }
881 # return $result;
882 #}
885 sub send_msg_to_target {
886 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
887 my $error = 0;
888 my $header;
889 my $timestamp = &get_time();
890 my $new_status;
891 my $act_status;
892 my ($sql_statement, $res);
894 if( $msg_header ) {
895 $header = "'$msg_header'-";
896 } else {
897 $header = "";
898 }
900 # Patch the source ip
901 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
902 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
903 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
904 }
906 # encrypt xml msg
907 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
909 # opensocket
910 my $socket = &open_socket($address);
911 if( !$socket ) {
912 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
913 $error++;
914 }
916 if( $error == 0 ) {
917 # send xml msg
918 print $socket $crypted_msg."\n";
920 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
921 daemon_log("$session_id DEBUG: message:\n$msg", 9);
923 }
925 # close socket in any case
926 if( $socket ) {
927 close $socket;
928 }
930 if( $error > 0 ) { $new_status = "down"; }
931 else { $new_status = $msg_header; }
934 # known_clients
935 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
936 $res = $known_clients_db->select_dbentry($sql_statement);
937 if( keys(%$res) == 1) {
938 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
939 if ($act_status eq "down" && $new_status eq "down") {
940 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
941 $res = $known_clients_db->del_dbentry($sql_statement);
942 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
943 } else {
944 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
945 $res = $known_clients_db->update_dbentry($sql_statement);
946 if($new_status eq "down"){
947 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
948 } else {
949 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
950 }
951 }
952 }
954 # known_server
955 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
956 $res = $known_server_db->select_dbentry($sql_statement);
957 if( keys(%$res) == 1) {
958 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
959 if ($act_status eq "down" && $new_status eq "down") {
960 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
961 $res = $known_server_db->del_dbentry($sql_statement);
962 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
963 }
964 else {
965 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
966 $res = $known_server_db->update_dbentry($sql_statement);
967 if($new_status eq "down"){
968 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
969 } else {
970 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
971 }
972 }
973 }
974 return $error;
975 }
978 sub update_jobdb_status_for_send_msgs {
979 my ($answer, $error) = @_;
980 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
981 my $jobdb_id = $1;
983 # sending msg faild
984 if( $error ) {
985 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
986 my $sql_statement = "UPDATE $job_queue_tn ".
987 "SET status='error', result='can not deliver msg, please consult log file' ".
988 "WHERE id=$jobdb_id";
989 my $res = $job_db->update_dbentry($sql_statement);
990 }
992 # sending msg was successful
993 } else {
994 my $sql_statement = "UPDATE $job_queue_tn ".
995 "SET status='done' ".
996 "WHERE id=$jobdb_id AND status='processed'";
997 my $res = $job_db->update_dbentry($sql_statement);
998 }
999 }
1000 }
1003 sub sig_handler {
1004 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1005 daemon_log("0 INFO got signal '$signal'", 1);
1006 $kernel->sig_handled();
1007 return;
1008 }
1011 sub msg_to_decrypt {
1012 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1013 my $session_id = $session->ID;
1014 my ($msg, $msg_hash, $module);
1015 my $error = 0;
1017 # hole neue msg aus @msgs_to_decrypt
1018 my $next_msg = shift @msgs_to_decrypt;
1020 # entschlüssle sie
1022 # msg is from a new client or gosa
1023 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1024 # msg is from a gosa-si-server
1025 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1026 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1027 }
1028 # msg is from a gosa-si-client
1029 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1030 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1031 }
1032 # an error occurred
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1035 # could not understand a msg from its server the client cause a re-registering process
1036 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1037 "' to cause a re-registering of the client if necessary", 3);
1038 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1039 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1040 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1041 my $host_name = $hit->{'hostname'};
1042 my $host_key = $hit->{'hostkey'};
1043 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1044 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1045 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1046 }
1047 $error++;
1048 }
1051 my $header;
1052 my $target;
1053 my $source;
1054 my $done = 0;
1055 my $sql;
1056 my $res;
1058 # check whether this message should be processed here
1059 if ($error == 0) {
1060 $header = @{$msg_hash->{'header'}}[0];
1061 $target = @{$msg_hash->{'target'}}[0];
1062 $source = @{$msg_hash->{'source'}}[0];
1063 my $not_found_in_known_clients_db = 0;
1064 my $not_found_in_known_server_db = 0;
1065 my $not_found_in_foreign_clients_db = 0;
1066 my $local_address;
1067 my $local_mac;
1068 my ($target_ip, $target_port) = split(':', $target);
1070 # Determine the local ip address if target is an ip address
1071 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1072 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1073 } else {
1074 $local_address = $server_address;
1075 }
1077 # Determine the local mac address if target is a mac address
1078 if ($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) {
1079 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1080 my $network_interface= &get_interface_for_ip($loc_ip);
1081 $local_mac = &get_mac_for_interface($network_interface);
1082 } else {
1083 $local_mac = $server_mac_address;
1084 }
1086 # target and source is equal to GOSA -> process here
1087 if (not $done) {
1088 if ($target eq "GOSA" && $source eq "GOSA") {
1089 $done = 1;
1090 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1091 }
1092 }
1094 # target is own address without forward_to_gosa-tag -> process here
1095 if (not $done) {
1096 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
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 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
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 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1115 if ($source eq "GOSA") {
1116 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1117 }
1118 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1120 } else {
1121 $not_found_in_known_clients_db = 1;
1122 }
1123 }
1125 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1126 if (not $done) {
1127 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1128 my $gosa_at;
1129 my $gosa_session_id;
1130 if (($target eq $local_address) && (defined $forward_to_gosa)){
1131 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1132 if ($gosa_at ne $local_address) {
1133 $done = 1;
1134 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1135 }
1136 }
1137 }
1139 # if message should be processed here -> add message to incoming_db
1140 if ($done) {
1141 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1142 # so gosa-si-server knows how to process this kind of messages
1143 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1144 $module = "GosaPackages";
1145 }
1147 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1148 primkey=>[],
1149 headertag=>$header,
1150 targettag=>$target,
1151 xmlmessage=>&encode_base64($msg),
1152 timestamp=>&get_time,
1153 module=>$module,
1154 sessionid=>$session_id,
1155 } );
1156 }
1158 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1159 if (not $done) {
1160 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1161 my $gosa_at;
1162 my $gosa_session_id;
1163 if (($target eq $local_address) && (defined $forward_to_gosa)){
1164 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1165 if ($gosa_at eq $local_address) {
1166 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1167 if( defined $session_reference ) {
1168 $heap = $session_reference->get_heap();
1169 }
1170 if(exists $heap->{'client'}) {
1171 $msg = &encrypt_msg($msg, $GosaPackages_key);
1172 $heap->{'client'}->put($msg);
1173 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1174 }
1175 $done = 1;
1176 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1177 }
1178 }
1180 }
1182 # target is a client address in foreign_clients -> forward to registration server
1183 if (not $done) {
1184 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1185 $res = $foreign_clients_db->select_dbentry($sql);
1186 if (keys(%$res) > 0) {
1187 my $hostname = $res->{1}->{'hostname'};
1188 my ($host_ip, $host_port) = split(/:/, $hostname);
1189 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1190 my $regserver = $res->{1}->{'regserver'};
1191 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1192 my $res = $known_server_db->select_dbentry($sql);
1193 if (keys(%$res) > 0) {
1194 my $regserver_key = $res->{1}->{'hostkey'};
1195 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1196 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1197 if ($source eq "GOSA") {
1198 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1199 }
1200 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1201 }
1202 $done = 1;
1203 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1204 } else {
1205 $not_found_in_foreign_clients_db = 1;
1206 }
1207 }
1209 # target is a server address -> forward to server
1210 if (not $done) {
1211 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1212 $res = $known_server_db->select_dbentry($sql);
1213 if (keys(%$res) > 0) {
1214 my $hostkey = $res->{1}->{'hostkey'};
1216 if ($source eq "GOSA") {
1217 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1218 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1220 }
1222 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1223 $done = 1;
1224 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1225 } else {
1226 $not_found_in_known_server_db = 1;
1227 }
1228 }
1231 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1232 if ( $not_found_in_foreign_clients_db
1233 && $not_found_in_known_server_db
1234 && $not_found_in_known_clients_db) {
1235 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1236 primkey=>[],
1237 headertag=>$header,
1238 targettag=>$target,
1239 xmlmessage=>&encode_base64($msg),
1240 timestamp=>&get_time,
1241 module=>$module,
1242 sessionid=>$session_id,
1243 } );
1244 $done = 1;
1245 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1246 }
1249 if (not $done) {
1250 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1251 if ($source eq "GOSA") {
1252 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1253 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1255 my $session_reference = $kernel->ID_id_to_session($session_id);
1256 if( defined $session_reference ) {
1257 $heap = $session_reference->get_heap();
1258 }
1259 if(exists $heap->{'client'}) {
1260 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1261 $heap->{'client'}->put($error_msg);
1262 }
1263 }
1264 }
1266 }
1268 return;
1269 }
1272 sub next_task {
1273 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1274 my $running_task = POE::Wheel::Run->new(
1275 Program => sub { process_task($session, $heap, $task) },
1276 StdioFilter => POE::Filter::Reference->new(),
1277 StdoutEvent => "task_result",
1278 StderrEvent => "task_debug",
1279 CloseEvent => "task_done",
1280 );
1281 $heap->{task}->{ $running_task->ID } = $running_task;
1282 }
1284 sub handle_task_result {
1285 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1286 my $client_answer = $result->{'answer'};
1287 if( $client_answer =~ s/session_id=(\d+)$// ) {
1288 my $session_id = $1;
1289 if( defined $session_id ) {
1290 my $session_reference = $kernel->ID_id_to_session($session_id);
1291 if( defined $session_reference ) {
1292 $heap = $session_reference->get_heap();
1293 }
1294 }
1296 if(exists $heap->{'client'}) {
1297 $heap->{'client'}->put($client_answer);
1298 }
1299 }
1300 $kernel->sig(CHLD => "child_reap");
1301 }
1303 sub handle_task_debug {
1304 my $result = $_[ARG0];
1305 print STDERR "$result\n";
1306 }
1308 sub handle_task_done {
1309 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1310 delete $heap->{task}->{$task_id};
1311 }
1313 sub process_task {
1314 no strict "refs";
1315 #CHECK: Not @_[...]?
1316 my ($session, $heap, $task) = @_;
1317 my $error = 0;
1318 my $answer_l;
1319 my ($answer_header, @answer_target_l, $answer_source);
1320 my $client_answer = "";
1322 # prepare all variables needed to process message
1323 #my $msg = $task->{'xmlmessage'};
1324 my $msg = &decode_base64($task->{'xmlmessage'});
1325 my $incoming_id = $task->{'id'};
1326 my $module = $task->{'module'};
1327 my $header = $task->{'headertag'};
1328 my $session_id = $task->{'sessionid'};
1329 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1330 my $source = @{$msg_hash->{'source'}}[0];
1332 # set timestamp of incoming client uptodate, so client will not
1333 # be deleted from known_clients because of expiration
1334 my $act_time = &get_time();
1335 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1336 my $res = $known_clients_db->exec_statement($sql);
1338 ######################
1339 # process incoming msg
1340 if( $error == 0) {
1341 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1342 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1343 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1345 if ( 0 < @{$answer_l} ) {
1346 my $answer_str = join("\n", @{$answer_l});
1347 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1348 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1349 }
1350 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1351 } else {
1352 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1353 }
1355 }
1356 if( !$answer_l ) { $error++ };
1358 ########
1359 # answer
1360 if( $error == 0 ) {
1362 foreach my $answer ( @{$answer_l} ) {
1363 # check outgoing msg to xml validity
1364 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1365 if( not defined $answer_hash ) { next; }
1367 $answer_header = @{$answer_hash->{'header'}}[0];
1368 @answer_target_l = @{$answer_hash->{'target'}};
1369 $answer_source = @{$answer_hash->{'source'}}[0];
1371 # deliver msg to all targets
1372 foreach my $answer_target ( @answer_target_l ) {
1374 # targets of msg are all gosa-si-clients in known_clients_db
1375 if( $answer_target eq "*" ) {
1376 # answer is for all clients
1377 my $sql_statement= "SELECT * FROM known_clients";
1378 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1379 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1380 my $host_name = $hit->{hostname};
1381 my $host_key = $hit->{hostkey};
1382 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1383 &update_jobdb_status_for_send_msgs($answer, $error);
1384 }
1385 }
1387 # targets of msg are all gosa-si-server in known_server_db
1388 elsif( $answer_target eq "KNOWN_SERVER" ) {
1389 # answer is for all server in known_server
1390 my $sql_statement= "SELECT * FROM $known_server_tn";
1391 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1392 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1393 my $host_name = $hit->{hostname};
1394 my $host_key = $hit->{hostkey};
1395 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1396 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1397 &update_jobdb_status_for_send_msgs($answer, $error);
1398 }
1399 }
1401 # target of msg is GOsa
1402 elsif( $answer_target eq "GOSA" ) {
1403 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1404 my $add_on = "";
1405 if( defined $session_id ) {
1406 $add_on = ".session_id=$session_id";
1407 }
1408 # answer is for GOSA and has to returned to connected client
1409 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1410 $client_answer = $gosa_answer.$add_on;
1411 }
1413 # target of msg is job queue at this host
1414 elsif( $answer_target eq "JOBDB") {
1415 $answer =~ /<header>(\S+)<\/header>/;
1416 my $header;
1417 if( defined $1 ) { $header = $1; }
1418 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1419 &update_jobdb_status_for_send_msgs($answer, $error);
1420 }
1422 # Target of msg is a mac address
1423 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 ) {
1424 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1425 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1426 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1427 my $found_ip_flag = 0;
1428 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1429 my $host_name = $hit->{hostname};
1430 my $host_key = $hit->{hostkey};
1431 $answer =~ s/$answer_target/$host_name/g;
1432 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1433 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1434 &update_jobdb_status_for_send_msgs($answer, $error);
1435 $found_ip_flag++ ;
1436 }
1437 if ($found_ip_flag == 0) {
1438 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1439 my $res = $foreign_clients_db->select_dbentry($sql);
1440 while( my ($hit_num, $hit) = each %{ $res } ) {
1441 my $host_name = $hit->{hostname};
1442 my $reg_server = $hit->{regserver};
1443 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1445 # Fetch key for reg_server
1446 my $reg_server_key;
1447 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1448 my $res = $known_server_db->select_dbentry($sql);
1449 if (exists $res->{1}) {
1450 $reg_server_key = $res->{1}->{'hostkey'};
1451 } else {
1452 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1453 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1454 $reg_server_key = undef;
1455 }
1457 # Send answer to server where client is registered
1458 if (defined $reg_server_key) {
1459 $answer =~ s/$answer_target/$host_name/g;
1460 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1461 &update_jobdb_status_for_send_msgs($answer, $error);
1462 $found_ip_flag++ ;
1463 }
1464 }
1465 }
1466 if( $found_ip_flag == 0) {
1467 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1468 }
1470 # Answer is for one specific host
1471 } else {
1472 # get encrypt_key
1473 my $encrypt_key = &get_encrypt_key($answer_target);
1474 if( not defined $encrypt_key ) {
1475 # unknown target
1476 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1477 next;
1478 }
1479 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1480 &update_jobdb_status_for_send_msgs($answer, $error);
1481 }
1482 }
1483 }
1484 }
1486 my $filter = POE::Filter::Reference->new();
1487 my %result = (
1488 status => "seems ok to me",
1489 answer => $client_answer,
1490 );
1492 my $output = $filter->put( [ \%result ] );
1493 print @$output;
1496 }
1498 sub session_start {
1499 my ($kernel) = $_[KERNEL];
1500 $global_kernel = $kernel;
1501 $kernel->yield('register_at_foreign_servers');
1502 $kernel->yield('create_fai_server_db', $fai_server_tn );
1503 $kernel->yield('create_fai_release_db', $fai_release_tn );
1504 $kernel->yield('watch_for_next_tasks');
1505 $kernel->sig(USR1 => "sig_handler");
1506 $kernel->sig(USR2 => "recreate_packages_db");
1507 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1508 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1509 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1510 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1511 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1512 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1513 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1515 # Start opsi check
1516 if ($opsi_enabled eq "true") {
1517 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1518 }
1520 }
1523 sub watch_for_done_jobs {
1524 #CHECK: $heap for what?
1525 my ($kernel,$heap) = @_[KERNEL, HEAP];
1527 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1528 my $res = $job_db->select_dbentry( $sql_statement );
1530 while( my ($id, $hit) = each %{$res} ) {
1531 my $jobdb_id = $hit->{id};
1532 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1533 my $res = $job_db->del_dbentry($sql_statement);
1534 }
1536 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1537 }
1540 sub watch_for_opsi_jobs {
1541 my ($kernel) = $_[KERNEL];
1543 # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a
1544 # opsi install job is to parse the xml message. There is still the correct header.
1545 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1546 my $res = $job_db->select_dbentry( $sql_statement );
1548 # Ask OPSI for an update of the running jobs
1549 while (my ($id, $hit) = each %$res ) {
1550 # Determine current parameters of the job
1551 my $hostId = $hit->{'plainname'};
1552 my $macaddress = $hit->{'macaddress'};
1553 my $progress = $hit->{'progress'};
1555 my $result= {};
1557 # For hosts, only return the products that are or get installed
1558 my $callobj;
1559 $callobj = {
1560 method => 'getProductStates_hash',
1561 params => [ $hostId ],
1562 id => 1,
1563 };
1565 my $hres = $opsi_client->call($opsi_url, $callobj);
1566 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1567 if (not &check_opsi_res($hres)) {
1568 my $htmp= $hres->result->{$hostId};
1570 # Check state != not_installed or action == setup -> load and add
1571 my $products= 0;
1572 my $installed= 0;
1573 my $installing = 0;
1574 my $error= 0;
1575 my @installed_list;
1576 my @error_list;
1577 my $act_status = "none";
1578 foreach my $product (@{$htmp}){
1580 if ($product->{'installationStatus'} ne "not_installed" or
1581 $product->{'actionRequest'} eq "setup"){
1583 # Increase number of products for this host
1584 $products++;
1586 if ($product->{'installationStatus'} eq "failed"){
1587 $result->{$product->{'productId'}}= "error";
1588 unshift(@error_list, $product->{'productId'});
1589 $error++;
1590 }
1591 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1592 $result->{$product->{'productId'}}= "installed";
1593 unshift(@installed_list, $product->{'productId'});
1594 $installed++;
1595 }
1596 if ($product->{'installationStatus'} eq "installing"){
1597 $result->{$product->{'productId'}}= "installing";
1598 $installing++;
1599 $act_status = "installing - ".$product->{'productId'};
1600 }
1601 }
1602 }
1604 # Estimate "rough" progress, avoid division by zero
1605 if ($products == 0) {
1606 $result->{'progress'}= 0;
1607 } else {
1608 $result->{'progress'}= int($installed * 100 / $products);
1609 }
1611 # Set updates in job queue
1612 if ((not $error) && (not $installing) && ($installed)) {
1613 $act_status = "installed - ".join(", ", @installed_list);
1614 }
1615 if ($error) {
1616 $act_status = "error - ".join(", ", @error_list);
1617 }
1618 if ($progress ne $result->{'progress'} ) {
1619 # Updating progress and result
1620 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1621 my $update_res = $job_db->update_dbentry($update_statement);
1622 }
1623 if ($progress eq 100) {
1624 # Updateing status
1625 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1626 if ($error) {
1627 $done_statement .= "status='error'";
1628 } else {
1629 $done_statement .= "status='done'";
1630 }
1631 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1632 my $done_res = $job_db->update_dbentry($done_statement);
1633 }
1636 }
1637 }
1639 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1640 }
1643 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1644 sub watch_for_modified_jobs {
1645 my ($kernel,$heap) = @_[KERNEL, HEAP];
1647 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1648 my $res = $job_db->select_dbentry( $sql_statement );
1650 # if db contains no jobs which should be update, do nothing
1651 if (keys %$res != 0) {
1653 if ($job_synchronization eq "true") {
1654 # make out of the db result a gosa-si message
1655 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1657 # update all other SI-server
1658 &inform_all_other_si_server($update_msg);
1659 }
1661 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1662 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1663 $res = $job_db->update_dbentry($sql_statement);
1664 }
1666 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1667 }
1670 sub watch_for_new_jobs {
1671 if($watch_for_new_jobs_in_progress == 0) {
1672 $watch_for_new_jobs_in_progress = 1;
1673 my ($kernel,$heap) = @_[KERNEL, HEAP];
1675 # check gosa job quaeue for jobs with executable timestamp
1676 my $timestamp = &get_time();
1677 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1678 my $res = $job_db->exec_statement( $sql_statement );
1680 # Merge all new jobs that would do the same actions
1681 my @drops;
1682 my $hits;
1683 foreach my $hit (reverse @{$res} ) {
1684 my $macaddress= lc @{$hit}[8];
1685 my $headertag= @{$hit}[5];
1686 if(
1687 defined($hits->{$macaddress}) &&
1688 defined($hits->{$macaddress}->{$headertag}) &&
1689 defined($hits->{$macaddress}->{$headertag}[0])
1690 ) {
1691 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1692 }
1693 $hits->{$macaddress}->{$headertag}= $hit;
1694 }
1696 # Delete new jobs with a matching job in state 'processing'
1697 foreach my $macaddress (keys %{$hits}) {
1698 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1699 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1700 if(defined($jobdb_id)) {
1701 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1702 my $res = $job_db->exec_statement( $sql_statement );
1703 foreach my $hit (@{$res}) {
1704 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1705 }
1706 } else {
1707 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1708 }
1709 }
1710 }
1712 # Commit deletion
1713 $job_db->exec_statementlist(\@drops);
1715 # Look for new jobs that could be executed
1716 foreach my $macaddress (keys %{$hits}) {
1718 # Look if there is an executing job
1719 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1720 my $res = $job_db->exec_statement( $sql_statement );
1722 # Skip new jobs for host if there is a processing job
1723 if(defined($res) and defined @{$res}[0]) {
1724 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1725 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1726 if(@{$row}[5] eq 'trigger_action_reinstall') {
1727 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1728 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1729 if(defined($res_2) and defined @{$res_2}[0]) {
1730 # Set status from goto-activation to 'waiting' and update timestamp
1731 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1732 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1733 }
1734 }
1735 next;
1736 }
1738 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1739 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1740 if(defined($jobdb_id)) {
1741 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1743 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1744 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1745 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1747 # expect macaddress is unique!!!!!!
1748 my $target = $res_hash->{1}->{hostname};
1750 # change header
1751 $job_msg =~ s/<header>job_/<header>gosa_/;
1753 # add sqlite_id
1754 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1756 $job_msg =~ /<header>(\S+)<\/header>/;
1757 my $header = $1 ;
1758 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1760 # update status in job queue to 'processing'
1761 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1762 my $res = $job_db->update_dbentry($sql_statement);
1763 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1765 # We don't want parallel processing
1766 last;
1767 }
1768 }
1769 }
1771 $watch_for_new_jobs_in_progress = 0;
1772 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1773 }
1774 }
1777 sub watch_for_new_messages {
1778 my ($kernel,$heap) = @_[KERNEL, HEAP];
1779 my @coll_user_msg; # collection list of outgoing messages
1781 # check messaging_db for new incoming messages with executable timestamp
1782 my $timestamp = &get_time();
1783 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1784 my $res = $messaging_db->exec_statement( $sql_statement );
1785 foreach my $hit (@{$res}) {
1787 # create outgoing messages
1788 my $message_to = @{$hit}[3];
1789 # translate message_to to plain login name
1790 my @message_to_l = split(/,/, $message_to);
1791 my %receiver_h;
1792 foreach my $receiver (@message_to_l) {
1793 if ($receiver =~ /^u_([\s\S]*)$/) {
1794 $receiver_h{$1} = 0;
1795 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1796 my $group_name = $1;
1797 # fetch all group members from ldap and add them to receiver hash
1798 my $ldap_handle = &get_ldap_handle();
1799 if (defined $ldap_handle) {
1800 my $mesg = $ldap_handle->search(
1801 base => $ldap_base,
1802 scope => 'sub',
1803 attrs => ['memberUid'],
1804 filter => "cn=$group_name",
1805 );
1806 if ($mesg->count) {
1807 my @entries = $mesg->entries;
1808 foreach my $entry (@entries) {
1809 my @receivers= $entry->get_value("memberUid");
1810 foreach my $receiver (@receivers) {
1811 $receiver_h{$1} = 0;
1812 }
1813 }
1814 }
1815 # translating errors ?
1816 if ($mesg->code) {
1817 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1818 }
1819 # ldap handle error ?
1820 } else {
1821 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1822 }
1823 } else {
1824 my $sbjct = &encode_base64(@{$hit}[1]);
1825 my $msg = &encode_base64(@{$hit}[7]);
1826 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1827 }
1828 }
1829 my @receiver_l = keys(%receiver_h);
1831 my $message_id = @{$hit}[0];
1833 #add each outgoing msg to messaging_db
1834 my $receiver;
1835 foreach $receiver (@receiver_l) {
1836 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1837 "VALUES ('".
1838 $message_id."', '". # id
1839 @{$hit}[1]."', '". # subject
1840 @{$hit}[2]."', '". # message_from
1841 $receiver."', '". # message_to
1842 "none"."', '". # flag
1843 "out"."', '". # direction
1844 @{$hit}[6]."', '". # delivery_time
1845 @{$hit}[7]."', '". # message
1846 $timestamp."'". # timestamp
1847 ")";
1848 &daemon_log("M DEBUG: $sql_statement", 1);
1849 my $res = $messaging_db->exec_statement($sql_statement);
1850 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1851 }
1853 # set incoming message to flag d=deliverd
1854 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1855 &daemon_log("M DEBUG: $sql_statement", 7);
1856 $res = $messaging_db->update_dbentry($sql_statement);
1857 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1858 }
1860 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1861 return;
1862 }
1864 sub watch_for_delivery_messages {
1865 my ($kernel, $heap) = @_[KERNEL, HEAP];
1867 # select outgoing messages
1868 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1869 #&daemon_log("0 DEBUG: $sql", 7);
1870 my $res = $messaging_db->exec_statement( $sql_statement );
1872 # build out msg for each usr
1873 foreach my $hit (@{$res}) {
1874 my $receiver = @{$hit}[3];
1875 my $msg_id = @{$hit}[0];
1876 my $subject = @{$hit}[1];
1877 my $message = @{$hit}[7];
1879 # resolve usr -> host where usr is logged in
1880 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1881 #&daemon_log("0 DEBUG: $sql", 7);
1882 my $res = $login_users_db->exec_statement($sql);
1884 # reciver is logged in nowhere
1885 if (not ref(@$res[0]) eq "ARRAY") { next; }
1887 my $send_succeed = 0;
1888 foreach my $hit (@$res) {
1889 my $receiver_host = @$hit[0];
1890 my $delivered2host = 0;
1891 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1893 # Looking for host in know_clients_db
1894 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1895 my $res = $known_clients_db->exec_statement($sql);
1897 # Host is known in known_clients_db
1898 if (ref(@$res[0]) eq "ARRAY") {
1899 my $receiver_key = @{@{$res}[0]}[2];
1900 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1901 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1902 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1903 if ($error == 0 ) {
1904 $send_succeed++ ;
1905 $delivered2host++ ;
1906 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1907 } else {
1908 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1909 }
1910 }
1912 # Message already send, do not need to do anything more, otherwise ...
1913 if ($delivered2host) { next;}
1915 # ...looking for host in foreign_clients_db
1916 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1917 $res = $foreign_clients_db->exec_statement($sql);
1919 # Host is known in foreign_clients_db
1920 if (ref(@$res[0]) eq "ARRAY") {
1921 my $registration_server = @{@{$res}[0]}[2];
1923 # Fetch encryption key for registration server
1924 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1925 my $res = $known_server_db->exec_statement($sql);
1926 if (ref(@$res[0]) eq "ARRAY") {
1927 my $registration_server_key = @{@{$res}[0]}[3];
1928 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1929 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1930 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1931 if ($error == 0 ) {
1932 $send_succeed++ ;
1933 $delivered2host++ ;
1934 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1935 } else {
1936 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1937 }
1939 } else {
1940 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1941 "registrated at server '$registration_server', ".
1942 "but no data available in known_server_db ", 1);
1943 }
1944 }
1946 if (not $delivered2host) {
1947 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1948 }
1949 }
1951 if ($send_succeed) {
1952 # set outgoing msg at db to deliverd
1953 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1954 my $res = $messaging_db->exec_statement($sql);
1955 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1956 } else {
1957 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
1958 }
1959 }
1961 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1962 return;
1963 }
1966 sub watch_for_done_messages {
1967 my ($kernel,$heap) = @_[KERNEL, HEAP];
1969 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1970 #&daemon_log("0 DEBUG: $sql", 7);
1971 my $res = $messaging_db->exec_statement($sql);
1973 foreach my $hit (@{$res}) {
1974 my $msg_id = @{$hit}[0];
1976 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1977 #&daemon_log("0 DEBUG: $sql", 7);
1978 my $res = $messaging_db->exec_statement($sql);
1980 # not all usr msgs have been seen till now
1981 if ( ref(@$res[0]) eq "ARRAY") { next; }
1983 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1984 #&daemon_log("0 DEBUG: $sql", 7);
1985 $res = $messaging_db->exec_statement($sql);
1987 }
1989 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1990 return;
1991 }
1994 sub watch_for_old_known_clients {
1995 my ($kernel,$heap) = @_[KERNEL, HEAP];
1997 my $sql_statement = "SELECT * FROM $known_clients_tn";
1998 my $res = $known_clients_db->select_dbentry( $sql_statement );
2000 my $act_time = int(&get_time());
2002 while ( my ($hit_num, $hit) = each %$res) {
2003 my $expired_timestamp = int($hit->{'timestamp'});
2004 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2005 my $dt = DateTime->new( year => $1,
2006 month => $2,
2007 day => $3,
2008 hour => $4,
2009 minute => $5,
2010 second => $6,
2011 );
2013 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2014 $expired_timestamp = $dt->ymd('').$dt->hms('');
2015 if ($act_time > $expired_timestamp) {
2016 my $hostname = $hit->{'hostname'};
2017 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2018 my $del_res = $known_clients_db->exec_statement($del_sql);
2020 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2021 }
2023 }
2025 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2026 }
2029 sub watch_for_next_tasks {
2030 my ($kernel,$heap) = @_[KERNEL, HEAP];
2032 my $sql = "SELECT * FROM $incoming_tn";
2033 my $res = $incoming_db->select_dbentry($sql);
2035 while ( my ($hit_num, $hit) = each %$res) {
2036 my $headertag = $hit->{'headertag'};
2037 if ($headertag =~ /^answer_(\d+)/) {
2038 # do not start processing, this message is for a still running POE::Wheel
2039 next;
2040 }
2041 my $message_id = $hit->{'id'};
2042 $kernel->yield('next_task', $hit);
2044 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2045 my $res = $incoming_db->exec_statement($sql);
2046 }
2048 $kernel->delay_set('watch_for_next_tasks', 0.1);
2049 }
2052 sub get_ldap_handle {
2053 my ($session_id) = @_;
2054 my $heap;
2055 my $ldap_handle;
2057 if (not defined $session_id ) { $session_id = 0 };
2058 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2060 if ($session_id == 0) {
2061 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2062 $ldap_handle = Net::LDAP->new( $ldap_uri );
2063 $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!");
2065 } else {
2066 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2067 if( defined $session_reference ) {
2068 $heap = $session_reference->get_heap();
2069 }
2071 if (not defined $heap) {
2072 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2073 return;
2074 }
2076 # TODO: This "if" is nonsense, because it doesn't prove that the
2077 # used handle is still valid - or if we've to reconnect...
2078 #if (not exists $heap->{ldap_handle}) {
2079 $ldap_handle = Net::LDAP->new( $ldap_uri );
2080 $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!");
2081 $heap->{ldap_handle} = $ldap_handle;
2082 #}
2083 }
2084 return $ldap_handle;
2085 }
2088 sub change_fai_state {
2089 my ($st, $targets, $session_id) = @_;
2090 $session_id = 0 if not defined $session_id;
2091 # Set FAI state to localboot
2092 my %mapActions= (
2093 reboot => '',
2094 update => 'softupdate',
2095 localboot => 'localboot',
2096 reinstall => 'install',
2097 rescan => '',
2098 wake => '',
2099 memcheck => 'memcheck',
2100 sysinfo => 'sysinfo',
2101 install => 'install',
2102 );
2104 # Return if this is unknown
2105 if (!exists $mapActions{ $st }){
2106 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2107 return;
2108 }
2110 my $state= $mapActions{ $st };
2112 my $ldap_handle = &get_ldap_handle($session_id);
2113 if( defined($ldap_handle) ) {
2115 # Build search filter for hosts
2116 my $search= "(&(objectClass=GOhard)";
2117 foreach (@{$targets}){
2118 $search.= "(macAddress=$_)";
2119 }
2120 $search.= ")";
2122 # If there's any host inside of the search string, procress them
2123 if (!($search =~ /macAddress/)){
2124 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2125 return;
2126 }
2128 # Perform search for Unit Tag
2129 my $mesg = $ldap_handle->search(
2130 base => $ldap_base,
2131 scope => 'sub',
2132 attrs => ['dn', 'FAIstate', 'objectClass'],
2133 filter => "$search"
2134 );
2136 if ($mesg->count) {
2137 my @entries = $mesg->entries;
2138 if (0 == @entries) {
2139 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2140 }
2142 foreach my $entry (@entries) {
2143 # Only modify entry if it is not set to '$state'
2144 if ($entry->get_value("FAIstate") ne "$state"){
2145 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2146 my $result;
2147 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2148 if (exists $tmp{'FAIobject'}){
2149 if ($state eq ''){
2150 $result= $ldap_handle->modify($entry->dn, changes => [
2151 delete => [ FAIstate => [] ] ]);
2152 } else {
2153 $result= $ldap_handle->modify($entry->dn, changes => [
2154 replace => [ FAIstate => $state ] ]);
2155 }
2156 } elsif ($state ne ''){
2157 $result= $ldap_handle->modify($entry->dn, changes => [
2158 add => [ objectClass => 'FAIobject' ],
2159 add => [ FAIstate => $state ] ]);
2160 }
2162 # Errors?
2163 if ($result->code){
2164 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2165 }
2166 } else {
2167 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2168 }
2169 }
2170 } else {
2171 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2172 }
2174 # if no ldap handle defined
2175 } else {
2176 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2177 }
2179 return;
2180 }
2183 sub change_goto_state {
2184 my ($st, $targets, $session_id) = @_;
2185 $session_id = 0 if not defined $session_id;
2187 # Switch on or off?
2188 my $state= $st eq 'active' ? 'active': 'locked';
2190 my $ldap_handle = &get_ldap_handle($session_id);
2191 if( defined($ldap_handle) ) {
2193 # Build search filter for hosts
2194 my $search= "(&(objectClass=GOhard)";
2195 foreach (@{$targets}){
2196 $search.= "(macAddress=$_)";
2197 }
2198 $search.= ")";
2200 # If there's any host inside of the search string, procress them
2201 if (!($search =~ /macAddress/)){
2202 return;
2203 }
2205 # Perform search for Unit Tag
2206 my $mesg = $ldap_handle->search(
2207 base => $ldap_base,
2208 scope => 'sub',
2209 attrs => ['dn', 'gotoMode'],
2210 filter => "$search"
2211 );
2213 if ($mesg->count) {
2214 my @entries = $mesg->entries;
2215 foreach my $entry (@entries) {
2217 # Only modify entry if it is not set to '$state'
2218 if ($entry->get_value("gotoMode") ne $state){
2220 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2221 my $result;
2222 $result= $ldap_handle->modify($entry->dn, changes => [
2223 replace => [ gotoMode => $state ] ]);
2225 # Errors?
2226 if ($result->code){
2227 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2228 }
2230 }
2231 }
2232 } else {
2233 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2234 }
2236 }
2237 }
2240 sub run_recreate_packages_db {
2241 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2242 my $session_id = $session->ID;
2243 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2244 $kernel->yield('create_fai_release_db', $fai_release_tn);
2245 $kernel->yield('create_fai_server_db', $fai_server_tn);
2246 return;
2247 }
2250 sub run_create_fai_server_db {
2251 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2252 my $session_id = $session->ID;
2253 my $task = POE::Wheel::Run->new(
2254 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2255 StdoutEvent => "session_run_result",
2256 StderrEvent => "session_run_debug",
2257 CloseEvent => "session_run_done",
2258 );
2260 $heap->{task}->{ $task->ID } = $task;
2261 return;
2262 }
2265 sub create_fai_server_db {
2266 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2267 my $result;
2269 if (not defined $session_id) { $session_id = 0; }
2270 my $ldap_handle = &get_ldap_handle();
2271 if(defined($ldap_handle)) {
2272 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2273 my $mesg= $ldap_handle->search(
2274 base => $ldap_base,
2275 scope => 'sub',
2276 attrs => ['FAIrepository', 'gosaUnitTag'],
2277 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2278 );
2279 if($mesg->{'resultCode'} == 0 &&
2280 $mesg->count != 0) {
2281 foreach my $entry (@{$mesg->{entries}}) {
2282 if($entry->exists('FAIrepository')) {
2283 # Add an entry for each Repository configured for server
2284 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2285 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2286 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2287 $result= $fai_server_db->add_dbentry( {
2288 table => $table_name,
2289 primkey => ['server', 'release', 'tag'],
2290 server => $tmp_url,
2291 release => $tmp_release,
2292 sections => $tmp_sections,
2293 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2294 } );
2295 }
2296 }
2297 }
2298 }
2299 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2301 # TODO: Find a way to post the 'create_packages_list_db' event
2302 if(not defined($dont_create_packages_list)) {
2303 &create_packages_list_db(undef, undef, $session_id);
2304 }
2305 }
2307 $ldap_handle->disconnect;
2308 return $result;
2309 }
2312 sub run_create_fai_release_db {
2313 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2314 my $session_id = $session->ID;
2315 my $task = POE::Wheel::Run->new(
2316 Program => sub { &create_fai_release_db($table_name, $session_id) },
2317 StdoutEvent => "session_run_result",
2318 StderrEvent => "session_run_debug",
2319 CloseEvent => "session_run_done",
2320 );
2322 $heap->{task}->{ $task->ID } = $task;
2323 return;
2324 }
2327 sub create_fai_release_db {
2328 my ($table_name, $session_id) = @_;
2329 my $result;
2331 # used for logging
2332 if (not defined $session_id) { $session_id = 0; }
2334 my $ldap_handle = &get_ldap_handle();
2335 if(defined($ldap_handle)) {
2336 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2337 my $mesg= $ldap_handle->search(
2338 base => $ldap_base,
2339 scope => 'sub',
2340 attrs => [],
2341 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2342 );
2343 if($mesg->{'resultCode'} == 0 &&
2344 $mesg->count != 0) {
2345 # Walk through all possible FAI container ou's
2346 my @sql_list;
2347 my $timestamp= &get_time();
2348 foreach my $ou (@{$mesg->{entries}}) {
2349 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2350 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2351 my @tmp_array=get_fai_release_entries($tmp_classes);
2352 if(@tmp_array) {
2353 foreach my $entry (@tmp_array) {
2354 if(defined($entry) && ref($entry) eq 'HASH') {
2355 my $sql=
2356 "INSERT INTO $table_name "
2357 ."(timestamp, release, class, type, state) VALUES ("
2358 .$timestamp.","
2359 ."'".$entry->{'release'}."',"
2360 ."'".$entry->{'class'}."',"
2361 ."'".$entry->{'type'}."',"
2362 ."'".$entry->{'state'}."')";
2363 push @sql_list, $sql;
2364 }
2365 }
2366 }
2367 }
2368 }
2370 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2371 if(@sql_list) {
2372 unshift @sql_list, "VACUUM";
2373 unshift @sql_list, "DELETE FROM $table_name";
2374 $fai_release_db->exec_statementlist(\@sql_list);
2375 }
2376 daemon_log("$session_id DEBUG: Done with inserting",7);
2377 }
2378 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2379 }
2380 $ldap_handle->disconnect;
2381 return $result;
2382 }
2384 sub get_fai_types {
2385 my $tmp_classes = shift || return undef;
2386 my @result;
2388 foreach my $type(keys %{$tmp_classes}) {
2389 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2390 my $entry = {
2391 type => $type,
2392 state => $tmp_classes->{$type}[0],
2393 };
2394 push @result, $entry;
2395 }
2396 }
2398 return @result;
2399 }
2401 sub get_fai_state {
2402 my $result = "";
2403 my $tmp_classes = shift || return $result;
2405 foreach my $type(keys %{$tmp_classes}) {
2406 if(defined($tmp_classes->{$type}[0])) {
2407 $result = $tmp_classes->{$type}[0];
2409 # State is equal for all types in class
2410 last;
2411 }
2412 }
2414 return $result;
2415 }
2417 sub resolve_fai_classes {
2418 my ($fai_base, $ldap_handle, $session_id) = @_;
2419 if (not defined $session_id) { $session_id = 0; }
2420 my $result;
2421 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2422 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2423 my $fai_classes;
2425 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2426 my $mesg= $ldap_handle->search(
2427 base => $fai_base,
2428 scope => 'sub',
2429 attrs => ['cn','objectClass','FAIstate'],
2430 filter => $fai_filter,
2431 );
2432 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2434 if($mesg->{'resultCode'} == 0 &&
2435 $mesg->count != 0) {
2436 foreach my $entry (@{$mesg->{entries}}) {
2437 if($entry->exists('cn')) {
2438 my $tmp_dn= $entry->dn();
2440 # Skip classname and ou dn parts for class
2441 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2443 # Skip classes without releases
2444 if((!defined($tmp_release)) || length($tmp_release)==0) {
2445 next;
2446 }
2448 my $tmp_cn= $entry->get_value('cn');
2449 my $tmp_state= $entry->get_value('FAIstate');
2451 my $tmp_type;
2452 # Get FAI type
2453 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2454 if(grep $_ eq $oclass, @possible_fai_classes) {
2455 $tmp_type= $oclass;
2456 last;
2457 }
2458 }
2460 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2461 # A Subrelease
2462 my @sub_releases = split(/,/, $tmp_release);
2464 # Walk through subreleases and build hash tree
2465 my $hash;
2466 while(my $tmp_sub_release = pop @sub_releases) {
2467 $hash .= "\{'$tmp_sub_release'\}->";
2468 }
2469 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2470 } else {
2471 # A branch, no subrelease
2472 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2473 }
2474 } elsif (!$entry->exists('cn')) {
2475 my $tmp_dn= $entry->dn();
2476 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2478 # Skip classes without releases
2479 if((!defined($tmp_release)) || length($tmp_release)==0) {
2480 next;
2481 }
2483 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2484 # A Subrelease
2485 my @sub_releases= split(/,/, $tmp_release);
2487 # Walk through subreleases and build hash tree
2488 my $hash;
2489 while(my $tmp_sub_release = pop @sub_releases) {
2490 $hash .= "\{'$tmp_sub_release'\}->";
2491 }
2492 # Remove the last two characters
2493 chop($hash);
2494 chop($hash);
2496 eval('$fai_classes->'.$hash.'= {}');
2497 } else {
2498 # A branch, no subrelease
2499 if(!exists($fai_classes->{$tmp_release})) {
2500 $fai_classes->{$tmp_release} = {};
2501 }
2502 }
2503 }
2504 }
2506 # The hash is complete, now we can honor the copy-on-write based missing entries
2507 foreach my $release (keys %$fai_classes) {
2508 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2509 }
2510 }
2511 return $result;
2512 }
2514 sub apply_fai_inheritance {
2515 my $fai_classes = shift || return {};
2516 my $tmp_classes;
2518 # Get the classes from the branch
2519 foreach my $class (keys %{$fai_classes}) {
2520 # Skip subreleases
2521 if($class =~ /^ou=.*$/) {
2522 next;
2523 } else {
2524 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2525 }
2526 }
2528 # Apply to each subrelease
2529 foreach my $subrelease (keys %{$fai_classes}) {
2530 if($subrelease =~ /ou=/) {
2531 foreach my $tmp_class (keys %{$tmp_classes}) {
2532 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2533 $fai_classes->{$subrelease}->{$tmp_class} =
2534 deep_copy($tmp_classes->{$tmp_class});
2535 } else {
2536 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2537 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2538 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2539 deep_copy($tmp_classes->{$tmp_class}->{$type});
2540 }
2541 }
2542 }
2543 }
2544 }
2545 }
2547 # Find subreleases in deeper levels
2548 foreach my $subrelease (keys %{$fai_classes}) {
2549 if($subrelease =~ /ou=/) {
2550 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2551 if($subsubrelease =~ /ou=/) {
2552 apply_fai_inheritance($fai_classes->{$subrelease});
2553 }
2554 }
2555 }
2556 }
2558 return $fai_classes;
2559 }
2561 sub get_fai_release_entries {
2562 my $tmp_classes = shift || return;
2563 my $parent = shift || "";
2564 my @result = shift || ();
2566 foreach my $entry (keys %{$tmp_classes}) {
2567 if(defined($entry)) {
2568 if($entry =~ /^ou=.*$/) {
2569 my $release_name = $entry;
2570 $release_name =~ s/ou=//g;
2571 if(length($parent)>0) {
2572 $release_name = $parent."/".$release_name;
2573 }
2574 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2575 foreach my $bufentry(@bufentries) {
2576 push @result, $bufentry;
2577 }
2578 } else {
2579 my @types = get_fai_types($tmp_classes->{$entry});
2580 foreach my $type (@types) {
2581 push @result,
2582 {
2583 'class' => $entry,
2584 'type' => $type->{'type'},
2585 'release' => $parent,
2586 'state' => $type->{'state'},
2587 };
2588 }
2589 }
2590 }
2591 }
2593 return @result;
2594 }
2596 sub deep_copy {
2597 my $this = shift;
2598 if (not ref $this) {
2599 $this;
2600 } elsif (ref $this eq "ARRAY") {
2601 [map deep_copy($_), @$this];
2602 } elsif (ref $this eq "HASH") {
2603 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2604 } else { die "what type is $_?" }
2605 }
2608 sub session_run_result {
2609 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2610 $kernel->sig(CHLD => "child_reap");
2611 }
2613 sub session_run_debug {
2614 my $result = $_[ARG0];
2615 print STDERR "$result\n";
2616 }
2618 sub session_run_done {
2619 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2620 delete $heap->{task}->{$task_id};
2621 }
2624 sub create_sources_list {
2625 my $session_id = shift;
2626 my $ldap_handle = &main::get_ldap_handle;
2627 my $result="/tmp/gosa_si_tmp_sources_list";
2629 # Remove old file
2630 if(stat($result)) {
2631 unlink($result);
2632 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2633 }
2635 my $fh;
2636 open($fh, ">$result");
2637 if (not defined $fh) {
2638 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2639 return undef;
2640 }
2641 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2642 my $mesg=$ldap_handle->search(
2643 base => $main::ldap_server_dn,
2644 scope => 'base',
2645 attrs => 'FAIrepository',
2646 filter => 'objectClass=FAIrepositoryServer'
2647 );
2648 if($mesg->count) {
2649 foreach my $entry(@{$mesg->{'entries'}}) {
2650 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2651 my ($server, $tag, $release, $sections)= split /\|/, $value;
2652 my $line = "deb $server $release";
2653 $sections =~ s/,/ /g;
2654 $line.= " $sections";
2655 print $fh $line."\n";
2656 }
2657 }
2658 }
2659 } else {
2660 if (defined $main::ldap_server_dn){
2661 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2662 } else {
2663 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2664 }
2665 }
2666 close($fh);
2668 return $result;
2669 }
2672 sub run_create_packages_list_db {
2673 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2674 my $session_id = $session->ID;
2676 my $task = POE::Wheel::Run->new(
2677 Priority => +20,
2678 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2679 StdoutEvent => "session_run_result",
2680 StderrEvent => "session_run_debug",
2681 CloseEvent => "session_run_done",
2682 );
2683 $heap->{task}->{ $task->ID } = $task;
2684 }
2687 sub create_packages_list_db {
2688 my ($ldap_handle, $sources_file, $session_id) = @_;
2690 # it should not be possible to trigger a recreation of packages_list_db
2691 # while packages_list_db is under construction, so set flag packages_list_under_construction
2692 # which is tested befor recreation can be started
2693 if (-r $packages_list_under_construction) {
2694 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2695 return;
2696 } else {
2697 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2698 # set packages_list_under_construction to true
2699 system("touch $packages_list_under_construction");
2700 @packages_list_statements=();
2701 }
2703 if (not defined $session_id) { $session_id = 0; }
2704 if (not defined $ldap_handle) {
2705 $ldap_handle= &get_ldap_handle();
2707 if (not defined $ldap_handle) {
2708 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2709 unlink($packages_list_under_construction);
2710 return;
2711 }
2712 }
2713 if (not defined $sources_file) {
2714 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2715 $sources_file = &create_sources_list($session_id);
2716 }
2718 if (not defined $sources_file) {
2719 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2720 unlink($packages_list_under_construction);
2721 return;
2722 }
2724 my $line;
2726 open(CONFIG, "<$sources_file") or do {
2727 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2728 unlink($packages_list_under_construction);
2729 return;
2730 };
2732 # Read lines
2733 while ($line = <CONFIG>){
2734 # Unify
2735 chop($line);
2736 $line =~ s/^\s+//;
2737 $line =~ s/^\s+/ /;
2739 # Strip comments
2740 $line =~ s/#.*$//g;
2742 # Skip empty lines
2743 if ($line =~ /^\s*$/){
2744 next;
2745 }
2747 # Interpret deb line
2748 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2749 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2750 my $section;
2751 foreach $section (split(' ', $sections)){
2752 &parse_package_info( $baseurl, $dist, $section, $session_id );
2753 }
2754 }
2755 }
2757 close (CONFIG);
2760 find(\&cleanup_and_extract, keys( %repo_dirs ));
2761 &main::strip_packages_list_statements();
2762 unshift @packages_list_statements, "VACUUM";
2763 $packages_list_db->exec_statementlist(\@packages_list_statements);
2764 unlink($packages_list_under_construction);
2765 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2766 return;
2767 }
2769 # This function should do some intensive task to minimize the db-traffic
2770 sub strip_packages_list_statements {
2771 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2772 my @new_statement_list=();
2773 my $hash;
2774 my $insert_hash;
2775 my $update_hash;
2776 my $delete_hash;
2777 my $local_timestamp=get_time();
2779 foreach my $existing_entry (@existing_entries) {
2780 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2781 }
2783 foreach my $statement (@packages_list_statements) {
2784 if($statement =~ /^INSERT/i) {
2785 # Assign the values from the insert statement
2786 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2787 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2788 if(exists($hash->{$distribution}->{$package}->{$version})) {
2789 # If section or description has changed, update the DB
2790 if(
2791 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2792 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2793 ) {
2794 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2795 }
2796 } else {
2797 # Insert a non-existing entry to db
2798 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2799 }
2800 } elsif ($statement =~ /^UPDATE/i) {
2801 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2802 /^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;
2803 foreach my $distribution (keys %{$hash}) {
2804 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2805 # update the insertion hash to execute only one query per package (insert instead insert+update)
2806 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2807 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2808 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2809 my $section;
2810 my $description;
2811 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2812 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2813 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2814 }
2815 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2816 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2817 }
2818 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2819 }
2820 }
2821 }
2822 }
2823 }
2825 # TODO: Check for orphaned entries
2827 # unroll the insert_hash
2828 foreach my $distribution (keys %{$insert_hash}) {
2829 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2830 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2831 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2832 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2833 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2834 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2835 ."'$local_timestamp')";
2836 }
2837 }
2838 }
2840 # unroll the update hash
2841 foreach my $distribution (keys %{$update_hash}) {
2842 foreach my $package (keys %{$update_hash->{$distribution}}) {
2843 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2844 my $set = "";
2845 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2846 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2847 }
2848 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2849 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2850 }
2851 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2852 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2853 }
2854 if(defined($set) and length($set) > 0) {
2855 $set .= "timestamp = '$local_timestamp'";
2856 } else {
2857 next;
2858 }
2859 push @new_statement_list,
2860 "UPDATE $main::packages_list_tn SET $set WHERE"
2861 ." distribution = '$distribution'"
2862 ." AND package = '$package'"
2863 ." AND version = '$version'";
2864 }
2865 }
2866 }
2868 @packages_list_statements = @new_statement_list;
2869 }
2872 sub parse_package_info {
2873 my ($baseurl, $dist, $section, $session_id)= @_;
2874 my ($package);
2875 if (not defined $session_id) { $session_id = 0; }
2876 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2877 $repo_dirs{ "${repo_path}/pool" } = 1;
2879 foreach $package ("Packages.gz"){
2880 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2881 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2882 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2883 }
2885 }
2888 sub get_package {
2889 my ($url, $dest, $session_id)= @_;
2890 if (not defined $session_id) { $session_id = 0; }
2892 my $tpath = dirname($dest);
2893 -d "$tpath" || mkpath "$tpath";
2895 # This is ugly, but I've no time to take a look at "how it works in perl"
2896 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2897 system("gunzip -cd '$dest' > '$dest.in'");
2898 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2899 unlink($dest);
2900 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2901 } else {
2902 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2903 }
2904 return 0;
2905 }
2908 sub parse_package {
2909 my ($path, $dist, $srv_path, $session_id)= @_;
2910 if (not defined $session_id) { $session_id = 0;}
2911 my ($package, $version, $section, $description);
2912 my $PACKAGES;
2913 my $timestamp = &get_time();
2915 if(not stat("$path.in")) {
2916 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2917 return;
2918 }
2920 open($PACKAGES, "<$path.in");
2921 if(not defined($PACKAGES)) {
2922 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2923 return;
2924 }
2926 # Read lines
2927 while (<$PACKAGES>){
2928 my $line = $_;
2929 # Unify
2930 chop($line);
2932 # Use empty lines as a trigger
2933 if ($line =~ /^\s*$/){
2934 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2935 push(@packages_list_statements, $sql);
2936 $package = "none";
2937 $version = "none";
2938 $section = "none";
2939 $description = "none";
2940 next;
2941 }
2943 # Trigger for package name
2944 if ($line =~ /^Package:\s/){
2945 ($package)= ($line =~ /^Package: (.*)$/);
2946 next;
2947 }
2949 # Trigger for version
2950 if ($line =~ /^Version:\s/){
2951 ($version)= ($line =~ /^Version: (.*)$/);
2952 next;
2953 }
2955 # Trigger for description
2956 if ($line =~ /^Description:\s/){
2957 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2958 next;
2959 }
2961 # Trigger for section
2962 if ($line =~ /^Section:\s/){
2963 ($section)= ($line =~ /^Section: (.*)$/);
2964 next;
2965 }
2967 # Trigger for filename
2968 if ($line =~ /^Filename:\s/){
2969 my ($filename) = ($line =~ /^Filename: (.*)$/);
2970 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2971 next;
2972 }
2973 }
2975 close( $PACKAGES );
2976 unlink( "$path.in" );
2977 }
2980 sub store_fileinfo {
2981 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2983 my %fileinfo = (
2984 'package' => $package,
2985 'dist' => $dist,
2986 'version' => $vers,
2987 );
2989 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2990 }
2993 sub cleanup_and_extract {
2994 my $fileinfo = $repo_files{ $File::Find::name };
2996 if( defined $fileinfo ) {
2998 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2999 my $sql;
3000 my $package = $fileinfo->{ 'package' };
3001 my $newver = $fileinfo->{ 'version' };
3003 mkpath($dir);
3004 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3006 if( -f "$dir/DEBIAN/templates" ) {
3008 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3010 my $tmpl= "";
3011 {
3012 local $/=undef;
3013 open FILE, "$dir/DEBIAN/templates";
3014 $tmpl = &encode_base64(<FILE>);
3015 close FILE;
3016 }
3017 rmtree("$dir/DEBIAN/templates");
3019 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3020 push @packages_list_statements, $sql;
3021 }
3022 }
3024 return;
3025 }
3028 sub register_at_foreign_servers {
3029 my ($kernel) = $_[KERNEL];
3031 # hole alle bekannten server aus known_server_db
3032 my $server_sql = "SELECT * FROM $known_server_tn";
3033 my $server_res = $known_server_db->exec_statement($server_sql);
3035 # no entries in known_server_db
3036 if (not ref(@$server_res[0]) eq "ARRAY") {
3037 # TODO
3038 }
3040 # detect already connected clients
3041 my $client_sql = "SELECT * FROM $known_clients_tn";
3042 my $client_res = $known_clients_db->exec_statement($client_sql);
3044 # send my server details to all other gosa-si-server within the network
3045 foreach my $hit (@$server_res) {
3046 my $hostname = @$hit[0];
3047 my $hostkey = &create_passwd;
3049 # add already connected clients to registration message
3050 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3051 &add_content2xml_hash($myhash, 'key', $hostkey);
3052 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3054 # add locally loaded gosa-si modules to registration message
3055 my $loaded_modules = {};
3056 while (my ($package, $pck_info) = each %$known_modules) {
3057 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3058 foreach my $act_module (keys(%{@$pck_info[2]})) {
3059 $loaded_modules->{$act_module} = "";
3060 }
3061 }
3063 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3065 # add macaddress to registration message
3066 my ($host_ip, $host_port) = split(/:/, $hostname);
3067 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3068 my $network_interface= &get_interface_for_ip($local_ip);
3069 my $host_mac = &get_mac_for_interface($network_interface);
3070 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3072 # build registration message and send it
3073 my $foreign_server_msg = &create_xml_string($myhash);
3074 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3075 }
3077 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3078 return;
3079 }
3082 #==== MAIN = main ==============================================================
3083 # parse commandline options
3084 Getopt::Long::Configure( "bundling" );
3085 GetOptions("h|help" => \&usage,
3086 "c|config=s" => \$cfg_file,
3087 "f|foreground" => \$foreground,
3088 "v|verbose+" => \$verbose,
3089 "no-arp+" => \$no_arp,
3090 );
3092 # read and set config parameters
3093 &check_cmdline_param ;
3094 &read_configfile($cfg_file, %cfg_defaults);
3095 &check_pid;
3097 $SIG{CHLD} = 'IGNORE';
3099 # forward error messages to logfile
3100 if( ! $foreground ) {
3101 open( STDIN, '+>/dev/null' );
3102 open( STDOUT, '+>&STDIN' );
3103 open( STDERR, '+>&STDIN' );
3104 }
3106 # Just fork, if we are not in foreground mode
3107 if( ! $foreground ) {
3108 chdir '/' or die "Can't chdir to /: $!";
3109 $pid = fork;
3110 setsid or die "Can't start a new session: $!";
3111 umask 0;
3112 } else {
3113 $pid = $$;
3114 }
3116 # Do something useful - put our PID into the pid_file
3117 if( 0 != $pid ) {
3118 open( LOCK_FILE, ">$pid_file" );
3119 print LOCK_FILE "$pid\n";
3120 close( LOCK_FILE );
3121 if( !$foreground ) {
3122 exit( 0 )
3123 };
3124 }
3126 # parse head url and revision from svn
3127 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3128 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3129 $server_headURL = defined $1 ? $1 : 'unknown' ;
3130 $server_revision = defined $2 ? $2 : 'unknown' ;
3131 if ($server_headURL =~ /\/tag\// ||
3132 $server_headURL =~ /\/branches\// ) {
3133 $server_status = "stable";
3134 } else {
3135 $server_status = "developmental" ;
3136 }
3139 daemon_log(" ", 1);
3140 daemon_log("$0 started!", 1);
3141 daemon_log("status: $server_status", 1);
3142 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3144 # connect to incoming_db
3145 unlink($incoming_file_name);
3146 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3147 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3149 # connect to gosa-si job queue
3150 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3151 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3153 # connect to known_clients_db
3154 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3155 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3157 # connect to foreign_clients_db
3158 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3159 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3161 # connect to known_server_db
3162 unlink($known_server_file_name);
3163 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3164 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3166 # connect to login_usr_db
3167 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3168 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3170 # connect to fai_server_db and fai_release_db
3171 unlink($fai_server_file_name);
3172 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3173 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3175 unlink($fai_release_file_name);
3176 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3177 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3179 # connect to packages_list_db
3180 #unlink($packages_list_file_name);
3181 unlink($packages_list_under_construction);
3182 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3183 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3185 # connect to messaging_db
3186 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3187 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3190 # create xml object used for en/decrypting
3191 $xml = new XML::Simple();
3194 # foreign servers
3195 my @foreign_server_list;
3197 # add foreign server from cfg file
3198 if ($foreign_server_string ne "") {
3199 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3200 foreach my $foreign_server (@cfg_foreign_server_list) {
3201 push(@foreign_server_list, $foreign_server);
3202 }
3203 }
3205 # add foreign server from dns
3206 my @tmp_servers;
3207 if ( !$server_domain) {
3208 # Try our DNS Searchlist
3209 for my $domain(get_dns_domains()) {
3210 chomp($domain);
3211 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3212 if(@$tmp_domains) {
3213 for my $tmp_server(@$tmp_domains) {
3214 push @tmp_servers, $tmp_server;
3215 }
3216 }
3217 }
3218 if(@tmp_servers && length(@tmp_servers)==0) {
3219 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3220 }
3221 } else {
3222 @tmp_servers = &get_server_addresses($server_domain);
3223 if( 0 == @tmp_servers ) {
3224 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3225 }
3226 }
3227 foreach my $server (@tmp_servers) {
3228 unshift(@foreign_server_list, $server);
3229 }
3230 # eliminate duplicate entries
3231 @foreign_server_list = &del_doubles(@foreign_server_list);
3232 my $all_foreign_server = join(", ", @foreign_server_list);
3233 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3235 # add all found foreign servers to known_server
3236 my $act_timestamp = &get_time();
3237 foreach my $foreign_server (@foreign_server_list) {
3239 # do not add myself to known_server_db
3240 if (&is_local($foreign_server)) { next; }
3241 ######################################
3243 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3244 primkey=>['hostname'],
3245 hostname=>$foreign_server,
3246 macaddress=>"",
3247 status=>'not_jet_registered',
3248 hostkey=>"none",
3249 loaded_modules => "none",
3250 timestamp=>$act_timestamp,
3251 } );
3252 }
3255 # Import all modules
3256 &import_modules;
3258 # Check wether all modules are gosa-si valid passwd check
3259 &password_check;
3261 # Prepare for using Opsi
3262 if ($opsi_enabled eq "true") {
3263 use JSON::RPC::Client;
3264 use XML::Quote qw(:all);
3265 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3266 $opsi_client = new JSON::RPC::Client;
3267 }
3270 POE::Component::Server::TCP->new(
3271 Alias => "TCP_SERVER",
3272 Port => $server_port,
3273 ClientInput => sub {
3274 my ($kernel, $input) = @_[KERNEL, ARG0];
3275 push(@tasks, $input);
3276 push(@msgs_to_decrypt, $input);
3277 $kernel->yield("msg_to_decrypt");
3278 },
3279 InlineStates => {
3280 msg_to_decrypt => \&msg_to_decrypt,
3281 next_task => \&next_task,
3282 task_result => \&handle_task_result,
3283 task_done => \&handle_task_done,
3284 task_debug => \&handle_task_debug,
3285 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3286 }
3287 );
3289 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3291 # create session for repeatedly checking the job queue for jobs
3292 POE::Session->create(
3293 inline_states => {
3294 _start => \&session_start,
3295 register_at_foreign_servers => \®ister_at_foreign_servers,
3296 sig_handler => \&sig_handler,
3297 next_task => \&next_task,
3298 task_result => \&handle_task_result,
3299 task_done => \&handle_task_done,
3300 task_debug => \&handle_task_debug,
3301 watch_for_next_tasks => \&watch_for_next_tasks,
3302 watch_for_new_messages => \&watch_for_new_messages,
3303 watch_for_delivery_messages => \&watch_for_delivery_messages,
3304 watch_for_done_messages => \&watch_for_done_messages,
3305 watch_for_new_jobs => \&watch_for_new_jobs,
3306 watch_for_modified_jobs => \&watch_for_modified_jobs,
3307 watch_for_done_jobs => \&watch_for_done_jobs,
3308 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3309 watch_for_old_known_clients => \&watch_for_old_known_clients,
3310 create_packages_list_db => \&run_create_packages_list_db,
3311 create_fai_server_db => \&run_create_fai_server_db,
3312 create_fai_release_db => \&run_create_fai_release_db,
3313 recreate_packages_db => \&run_recreate_packages_db,
3314 session_run_result => \&session_run_result,
3315 session_run_debug => \&session_run_debug,
3316 session_run_done => \&session_run_done,
3317 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3318 }
3319 );
3322 POE::Kernel->run();
3323 exit;