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 }
1091 }
1093 # target is own address without forward_to_gosa-tag -> process here
1094 if (not $done) {
1095 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1096 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1097 $done = 1;
1098 if ($source eq "GOSA") {
1099 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1100 }
1101 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1102 }
1103 }
1105 # target is a client address in known_clients -> process here
1106 if (not $done) {
1107 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1108 $res = $known_clients_db->select_dbentry($sql);
1109 if (keys(%$res) > 0) {
1110 $done = 1;
1111 my $hostname = $res->{1}->{'hostname'};
1112 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1113 #print STDERR "target is a client address in known_clients -> process here\n";
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 }
1119 } else {
1120 $not_found_in_known_clients_db = 1;
1121 }
1122 }
1124 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1125 if (not $done) {
1126 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1127 my $gosa_at;
1128 my $gosa_session_id;
1129 if (($target eq $local_address) && (defined $forward_to_gosa)){
1130 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1131 if ($gosa_at ne $local_address) {
1132 $done = 1;
1133 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1134 }
1135 }
1136 }
1138 # if message should be processed here -> add message to incoming_db
1139 if ($done) {
1140 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1141 # so gosa-si-server knows how to process this kind of messages
1142 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1143 $module = "GosaPackages";
1144 }
1146 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1147 primkey=>[],
1148 headertag=>$header,
1149 targettag=>$target,
1150 xmlmessage=>&encode_base64($msg),
1151 timestamp=>&get_time,
1152 module=>$module,
1153 sessionid=>$session_id,
1154 } );
1155 }
1157 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1158 if (not $done) {
1159 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1160 my $gosa_at;
1161 my $gosa_session_id;
1162 if (($target eq $local_address) && (defined $forward_to_gosa)){
1163 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1164 if ($gosa_at eq $local_address) {
1165 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1166 if( defined $session_reference ) {
1167 $heap = $session_reference->get_heap();
1168 }
1169 if(exists $heap->{'client'}) {
1170 $msg = &encrypt_msg($msg, $GosaPackages_key);
1171 $heap->{'client'}->put($msg);
1172 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1173 }
1174 $done = 1;
1175 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1176 }
1177 }
1179 }
1181 # target is a client address in foreign_clients -> forward to registration server
1182 if (not $done) {
1183 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1184 $res = $foreign_clients_db->select_dbentry($sql);
1185 if (keys(%$res) > 0) {
1186 my $hostname = $res->{1}->{'hostname'};
1187 my ($host_ip, $host_port) = split(/:/, $hostname);
1188 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1189 my $regserver = $res->{1}->{'regserver'};
1190 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1191 my $res = $known_server_db->select_dbentry($sql);
1192 if (keys(%$res) > 0) {
1193 my $regserver_key = $res->{1}->{'hostkey'};
1194 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1195 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1196 if ($source eq "GOSA") {
1197 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1198 }
1199 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1200 }
1201 $done = 1;
1202 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1203 } else {
1204 $not_found_in_foreign_clients_db = 1;
1205 }
1206 }
1208 # target is a server address -> forward to server
1209 if (not $done) {
1210 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1211 $res = $known_server_db->select_dbentry($sql);
1212 if (keys(%$res) > 0) {
1213 my $hostkey = $res->{1}->{'hostkey'};
1215 if ($source eq "GOSA") {
1216 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1217 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1219 }
1221 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1222 $done = 1;
1223 } else {
1224 $not_found_in_known_server_db = 1;
1225 }
1226 }
1229 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1230 if ( $not_found_in_foreign_clients_db
1231 && $not_found_in_known_server_db
1232 && $not_found_in_known_clients_db) {
1233 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1234 primkey=>[],
1235 headertag=>$header,
1236 targettag=>$target,
1237 xmlmessage=>&encode_base64($msg),
1238 timestamp=>&get_time,
1239 module=>$module,
1240 sessionid=>$session_id,
1241 } );
1242 $done = 1;
1243 }
1246 if (not $done) {
1247 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1248 if ($source eq "GOSA") {
1249 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1250 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1252 my $session_reference = $kernel->ID_id_to_session($session_id);
1253 if( defined $session_reference ) {
1254 $heap = $session_reference->get_heap();
1255 }
1256 if(exists $heap->{'client'}) {
1257 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1258 $heap->{'client'}->put($error_msg);
1259 }
1260 }
1261 }
1263 }
1265 return;
1266 }
1269 sub next_task {
1270 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1271 my $running_task = POE::Wheel::Run->new(
1272 Program => sub { process_task($session, $heap, $task) },
1273 StdioFilter => POE::Filter::Reference->new(),
1274 StdoutEvent => "task_result",
1275 StderrEvent => "task_debug",
1276 CloseEvent => "task_done",
1277 );
1278 $heap->{task}->{ $running_task->ID } = $running_task;
1279 }
1281 sub handle_task_result {
1282 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1283 my $client_answer = $result->{'answer'};
1284 if( $client_answer =~ s/session_id=(\d+)$// ) {
1285 my $session_id = $1;
1286 if( defined $session_id ) {
1287 my $session_reference = $kernel->ID_id_to_session($session_id);
1288 if( defined $session_reference ) {
1289 $heap = $session_reference->get_heap();
1290 }
1291 }
1293 if(exists $heap->{'client'}) {
1294 $heap->{'client'}->put($client_answer);
1295 }
1296 }
1297 $kernel->sig(CHLD => "child_reap");
1298 }
1300 sub handle_task_debug {
1301 my $result = $_[ARG0];
1302 print STDERR "$result\n";
1303 }
1305 sub handle_task_done {
1306 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1307 delete $heap->{task}->{$task_id};
1308 }
1310 sub process_task {
1311 no strict "refs";
1312 #CHECK: Not @_[...]?
1313 my ($session, $heap, $task) = @_;
1314 my $error = 0;
1315 my $answer_l;
1316 my ($answer_header, @answer_target_l, $answer_source);
1317 my $client_answer = "";
1319 # prepare all variables needed to process message
1320 #my $msg = $task->{'xmlmessage'};
1321 my $msg = &decode_base64($task->{'xmlmessage'});
1322 my $incoming_id = $task->{'id'};
1323 my $module = $task->{'module'};
1324 my $header = $task->{'headertag'};
1325 my $session_id = $task->{'sessionid'};
1326 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1327 my $source = @{$msg_hash->{'source'}}[0];
1329 # set timestamp of incoming client uptodate, so client will not
1330 # be deleted from known_clients because of expiration
1331 my $act_time = &get_time();
1332 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1333 my $res = $known_clients_db->exec_statement($sql);
1335 ######################
1336 # process incoming msg
1337 if( $error == 0) {
1338 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1339 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1340 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1342 if ( 0 < @{$answer_l} ) {
1343 my $answer_str = join("\n", @{$answer_l});
1344 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1345 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1346 }
1347 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1348 } else {
1349 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1350 }
1352 }
1353 if( !$answer_l ) { $error++ };
1355 ########
1356 # answer
1357 if( $error == 0 ) {
1359 foreach my $answer ( @{$answer_l} ) {
1360 # check outgoing msg to xml validity
1361 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1362 if( not defined $answer_hash ) { next; }
1364 $answer_header = @{$answer_hash->{'header'}}[0];
1365 @answer_target_l = @{$answer_hash->{'target'}};
1366 $answer_source = @{$answer_hash->{'source'}}[0];
1368 # deliver msg to all targets
1369 foreach my $answer_target ( @answer_target_l ) {
1371 # targets of msg are all gosa-si-clients in known_clients_db
1372 if( $answer_target eq "*" ) {
1373 # answer is for all clients
1374 my $sql_statement= "SELECT * FROM known_clients";
1375 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1376 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1377 my $host_name = $hit->{hostname};
1378 my $host_key = $hit->{hostkey};
1379 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1380 &update_jobdb_status_for_send_msgs($answer, $error);
1381 }
1382 }
1384 # targets of msg are all gosa-si-server in known_server_db
1385 elsif( $answer_target eq "KNOWN_SERVER" ) {
1386 # answer is for all server in known_server
1387 my $sql_statement= "SELECT * FROM $known_server_tn";
1388 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1389 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1390 my $host_name = $hit->{hostname};
1391 my $host_key = $hit->{hostkey};
1392 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1393 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1394 &update_jobdb_status_for_send_msgs($answer, $error);
1395 }
1396 }
1398 # target of msg is GOsa
1399 elsif( $answer_target eq "GOSA" ) {
1400 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1401 my $add_on = "";
1402 if( defined $session_id ) {
1403 $add_on = ".session_id=$session_id";
1404 }
1405 # answer is for GOSA and has to returned to connected client
1406 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1407 $client_answer = $gosa_answer.$add_on;
1408 }
1410 # target of msg is job queue at this host
1411 elsif( $answer_target eq "JOBDB") {
1412 $answer =~ /<header>(\S+)<\/header>/;
1413 my $header;
1414 if( defined $1 ) { $header = $1; }
1415 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1416 &update_jobdb_status_for_send_msgs($answer, $error);
1417 }
1419 # Target of msg is a mac address
1420 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 ) {
1421 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1422 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1423 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1424 my $found_ip_flag = 0;
1425 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1426 my $host_name = $hit->{hostname};
1427 my $host_key = $hit->{hostkey};
1428 $answer =~ s/$answer_target/$host_name/g;
1429 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1430 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1431 &update_jobdb_status_for_send_msgs($answer, $error);
1432 $found_ip_flag++ ;
1433 }
1434 if ($found_ip_flag == 0) {
1435 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1436 my $res = $foreign_clients_db->select_dbentry($sql);
1437 while( my ($hit_num, $hit) = each %{ $res } ) {
1438 my $host_name = $hit->{hostname};
1439 my $reg_server = $hit->{regserver};
1440 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1442 # Fetch key for reg_server
1443 my $reg_server_key;
1444 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1445 my $res = $known_server_db->select_dbentry($sql);
1446 if (exists $res->{1}) {
1447 $reg_server_key = $res->{1}->{'hostkey'};
1448 } else {
1449 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1450 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1451 $reg_server_key = undef;
1452 }
1454 # Send answer to server where client is registered
1455 if (defined $reg_server_key) {
1456 $answer =~ s/$answer_target/$host_name/g;
1457 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1458 &update_jobdb_status_for_send_msgs($answer, $error);
1459 $found_ip_flag++ ;
1460 }
1461 }
1462 }
1463 if( $found_ip_flag == 0) {
1464 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1465 }
1467 # Answer is for one specific host
1468 } else {
1469 # get encrypt_key
1470 my $encrypt_key = &get_encrypt_key($answer_target);
1471 if( not defined $encrypt_key ) {
1472 # unknown target
1473 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1474 next;
1475 }
1476 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1477 &update_jobdb_status_for_send_msgs($answer, $error);
1478 }
1479 }
1480 }
1481 }
1483 my $filter = POE::Filter::Reference->new();
1484 my %result = (
1485 status => "seems ok to me",
1486 answer => $client_answer,
1487 );
1489 my $output = $filter->put( [ \%result ] );
1490 print @$output;
1493 }
1495 sub session_start {
1496 my ($kernel) = $_[KERNEL];
1497 $global_kernel = $kernel;
1498 $kernel->yield('register_at_foreign_servers');
1499 $kernel->yield('create_fai_server_db', $fai_server_tn );
1500 $kernel->yield('create_fai_release_db', $fai_release_tn );
1501 $kernel->yield('watch_for_next_tasks');
1502 $kernel->sig(USR1 => "sig_handler");
1503 $kernel->sig(USR2 => "recreate_packages_db");
1504 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1505 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1506 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1507 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1508 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1509 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1510 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1512 # Start opsi check
1513 if ($opsi_enabled eq "true") {
1514 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1515 }
1517 }
1520 sub watch_for_done_jobs {
1521 #CHECK: $heap for what?
1522 my ($kernel,$heap) = @_[KERNEL, HEAP];
1524 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1525 my $res = $job_db->select_dbentry( $sql_statement );
1527 while( my ($id, $hit) = each %{$res} ) {
1528 my $jobdb_id = $hit->{id};
1529 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1530 my $res = $job_db->del_dbentry($sql_statement);
1531 }
1533 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1534 }
1537 sub watch_for_opsi_jobs {
1538 my ($kernel) = $_[KERNEL];
1540 # 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
1541 # opsi install job is to parse the xml message. There is still the correct header.
1542 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1543 my $res = $job_db->select_dbentry( $sql_statement );
1545 # Ask OPSI for an update of the running jobs
1546 while (my ($id, $hit) = each %$res ) {
1547 # Determine current parameters of the job
1548 my $hostId = $hit->{'plainname'};
1549 my $macaddress = $hit->{'macaddress'};
1550 my $progress = $hit->{'progress'};
1552 my $result= {};
1554 # For hosts, only return the products that are or get installed
1555 my $callobj;
1556 $callobj = {
1557 method => 'getProductStates_hash',
1558 params => [ $hostId ],
1559 id => 1,
1560 };
1562 my $hres = $opsi_client->call($opsi_url, $callobj);
1563 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1564 if (not &check_opsi_res($hres)) {
1565 my $htmp= $hres->result->{$hostId};
1567 # Check state != not_installed or action == setup -> load and add
1568 my $products= 0;
1569 my $installed= 0;
1570 my $installing = 0;
1571 my $error= 0;
1572 my @installed_list;
1573 my @error_list;
1574 my $act_status = "none";
1575 foreach my $product (@{$htmp}){
1577 if ($product->{'installationStatus'} ne "not_installed" or
1578 $product->{'actionRequest'} eq "setup"){
1580 # Increase number of products for this host
1581 $products++;
1583 if ($product->{'installationStatus'} eq "failed"){
1584 $result->{$product->{'productId'}}= "error";
1585 unshift(@error_list, $product->{'productId'});
1586 $error++;
1587 }
1588 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1589 $result->{$product->{'productId'}}= "installed";
1590 unshift(@installed_list, $product->{'productId'});
1591 $installed++;
1592 }
1593 if ($product->{'installationStatus'} eq "installing"){
1594 $result->{$product->{'productId'}}= "installing";
1595 $installing++;
1596 $act_status = "installing - ".$product->{'productId'};
1597 }
1598 }
1599 }
1601 # Estimate "rough" progress, avoid division by zero
1602 if ($products == 0) {
1603 $result->{'progress'}= 0;
1604 } else {
1605 $result->{'progress'}= int($installed * 100 / $products);
1606 }
1608 # Set updates in job queue
1609 if ((not $error) && (not $installing) && ($installed)) {
1610 $act_status = "installed - ".join(", ", @installed_list);
1611 }
1612 if ($error) {
1613 $act_status = "error - ".join(", ", @error_list);
1614 }
1615 if ($progress ne $result->{'progress'} ) {
1616 # Updating progress and result
1617 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1618 my $update_res = $job_db->update_dbentry($update_statement);
1619 }
1620 if ($progress eq 100) {
1621 # Updateing status
1622 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1623 if ($error) {
1624 $done_statement .= "status='error'";
1625 } else {
1626 $done_statement .= "status='done'";
1627 }
1628 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1629 my $done_res = $job_db->update_dbentry($done_statement);
1630 }
1633 }
1634 }
1636 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1637 }
1640 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1641 sub watch_for_modified_jobs {
1642 my ($kernel,$heap) = @_[KERNEL, HEAP];
1644 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1645 my $res = $job_db->select_dbentry( $sql_statement );
1647 # if db contains no jobs which should be update, do nothing
1648 if (keys %$res != 0) {
1650 if ($job_synchronization eq "true") {
1651 # make out of the db result a gosa-si message
1652 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1654 # update all other SI-server
1655 &inform_all_other_si_server($update_msg);
1656 }
1658 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1659 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1660 $res = $job_db->update_dbentry($sql_statement);
1661 }
1663 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1664 }
1667 sub watch_for_new_jobs {
1668 if($watch_for_new_jobs_in_progress == 0) {
1669 $watch_for_new_jobs_in_progress = 1;
1670 my ($kernel,$heap) = @_[KERNEL, HEAP];
1672 # check gosa job quaeue for jobs with executable timestamp
1673 my $timestamp = &get_time();
1674 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1675 my $res = $job_db->exec_statement( $sql_statement );
1677 # Merge all new jobs that would do the same actions
1678 my @drops;
1679 my $hits;
1680 foreach my $hit (reverse @{$res} ) {
1681 my $macaddress= lc @{$hit}[8];
1682 my $headertag= @{$hit}[5];
1683 if(
1684 defined($hits->{$macaddress}) &&
1685 defined($hits->{$macaddress}->{$headertag}) &&
1686 defined($hits->{$macaddress}->{$headertag}[0])
1687 ) {
1688 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1689 }
1690 $hits->{$macaddress}->{$headertag}= $hit;
1691 }
1693 # Delete new jobs with a matching job in state 'processing'
1694 foreach my $macaddress (keys %{$hits}) {
1695 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1696 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1697 if(defined($jobdb_id)) {
1698 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1699 my $res = $job_db->exec_statement( $sql_statement );
1700 foreach my $hit (@{$res}) {
1701 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1702 }
1703 } else {
1704 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1705 }
1706 }
1707 }
1709 # Commit deletion
1710 $job_db->exec_statementlist(\@drops);
1712 # Look for new jobs that could be executed
1713 foreach my $macaddress (keys %{$hits}) {
1715 # Look if there is an executing job
1716 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1717 my $res = $job_db->exec_statement( $sql_statement );
1719 # Skip new jobs for host if there is a processing job
1720 if(defined($res) and defined @{$res}[0]) {
1721 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1722 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1723 if(@{$row}[5] eq 'trigger_action_reinstall') {
1724 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1725 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1726 if(defined($res_2) and defined @{$res_2}[0]) {
1727 # Set status from goto-activation to 'waiting' and update timestamp
1728 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1729 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1730 }
1731 }
1732 next;
1733 }
1735 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1736 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1737 if(defined($jobdb_id)) {
1738 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1740 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1741 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1742 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1744 # expect macaddress is unique!!!!!!
1745 my $target = $res_hash->{1}->{hostname};
1747 # change header
1748 $job_msg =~ s/<header>job_/<header>gosa_/;
1750 # add sqlite_id
1751 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1753 $job_msg =~ /<header>(\S+)<\/header>/;
1754 my $header = $1 ;
1755 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1757 # update status in job queue to 'processing'
1758 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1759 my $res = $job_db->update_dbentry($sql_statement);
1760 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1762 # We don't want parallel processing
1763 last;
1764 }
1765 }
1766 }
1768 $watch_for_new_jobs_in_progress = 0;
1769 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1770 }
1771 }
1774 sub watch_for_new_messages {
1775 my ($kernel,$heap) = @_[KERNEL, HEAP];
1776 my @coll_user_msg; # collection list of outgoing messages
1778 # check messaging_db for new incoming messages with executable timestamp
1779 my $timestamp = &get_time();
1780 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1781 my $res = $messaging_db->exec_statement( $sql_statement );
1782 foreach my $hit (@{$res}) {
1784 # create outgoing messages
1785 my $message_to = @{$hit}[3];
1786 # translate message_to to plain login name
1787 my @message_to_l = split(/,/, $message_to);
1788 my %receiver_h;
1789 foreach my $receiver (@message_to_l) {
1790 if ($receiver =~ /^u_([\s\S]*)$/) {
1791 $receiver_h{$1} = 0;
1792 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1793 my $group_name = $1;
1794 # fetch all group members from ldap and add them to receiver hash
1795 my $ldap_handle = &get_ldap_handle();
1796 if (defined $ldap_handle) {
1797 my $mesg = $ldap_handle->search(
1798 base => $ldap_base,
1799 scope => 'sub',
1800 attrs => ['memberUid'],
1801 filter => "cn=$group_name",
1802 );
1803 if ($mesg->count) {
1804 my @entries = $mesg->entries;
1805 foreach my $entry (@entries) {
1806 my @receivers= $entry->get_value("memberUid");
1807 foreach my $receiver (@receivers) {
1808 $receiver_h{$1} = 0;
1809 }
1810 }
1811 }
1812 # translating errors ?
1813 if ($mesg->code) {
1814 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1815 }
1816 # ldap handle error ?
1817 } else {
1818 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1819 }
1820 } else {
1821 my $sbjct = &encode_base64(@{$hit}[1]);
1822 my $msg = &encode_base64(@{$hit}[7]);
1823 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1824 }
1825 }
1826 my @receiver_l = keys(%receiver_h);
1828 my $message_id = @{$hit}[0];
1830 #add each outgoing msg to messaging_db
1831 my $receiver;
1832 foreach $receiver (@receiver_l) {
1833 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1834 "VALUES ('".
1835 $message_id."', '". # id
1836 @{$hit}[1]."', '". # subject
1837 @{$hit}[2]."', '". # message_from
1838 $receiver."', '". # message_to
1839 "none"."', '". # flag
1840 "out"."', '". # direction
1841 @{$hit}[6]."', '". # delivery_time
1842 @{$hit}[7]."', '". # message
1843 $timestamp."'". # timestamp
1844 ")";
1845 &daemon_log("M DEBUG: $sql_statement", 1);
1846 my $res = $messaging_db->exec_statement($sql_statement);
1847 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1848 }
1850 # set incoming message to flag d=deliverd
1851 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1852 &daemon_log("M DEBUG: $sql_statement", 7);
1853 $res = $messaging_db->update_dbentry($sql_statement);
1854 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1855 }
1857 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1858 return;
1859 }
1861 sub watch_for_delivery_messages {
1862 my ($kernel, $heap) = @_[KERNEL, HEAP];
1864 # select outgoing messages
1865 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1866 #&daemon_log("0 DEBUG: $sql", 7);
1867 my $res = $messaging_db->exec_statement( $sql_statement );
1869 # build out msg for each usr
1870 foreach my $hit (@{$res}) {
1871 my $receiver = @{$hit}[3];
1872 my $msg_id = @{$hit}[0];
1873 my $subject = @{$hit}[1];
1874 my $message = @{$hit}[7];
1876 # resolve usr -> host where usr is logged in
1877 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1878 #&daemon_log("0 DEBUG: $sql", 7);
1879 my $res = $login_users_db->exec_statement($sql);
1881 # reciver is logged in nowhere
1882 if (not ref(@$res[0]) eq "ARRAY") { next; }
1884 my $send_succeed = 0;
1885 foreach my $hit (@$res) {
1886 my $receiver_host = @$hit[0];
1887 my $delivered2host = 0;
1888 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1890 # Looking for host in know_clients_db
1891 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1892 my $res = $known_clients_db->exec_statement($sql);
1894 # Host is known in known_clients_db
1895 if (ref(@$res[0]) eq "ARRAY") {
1896 my $receiver_key = @{@{$res}[0]}[2];
1897 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1898 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1899 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1900 if ($error == 0 ) {
1901 $send_succeed++ ;
1902 $delivered2host++ ;
1903 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1904 } else {
1905 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1906 }
1907 }
1909 # Message already send, do not need to do anything more, otherwise ...
1910 if ($delivered2host) { next;}
1912 # ...looking for host in foreign_clients_db
1913 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1914 $res = $foreign_clients_db->exec_statement($sql);
1916 # Host is known in foreign_clients_db
1917 if (ref(@$res[0]) eq "ARRAY") {
1918 my $registration_server = @{@{$res}[0]}[2];
1920 # Fetch encryption key for registration server
1921 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1922 my $res = $known_server_db->exec_statement($sql);
1923 if (ref(@$res[0]) eq "ARRAY") {
1924 my $registration_server_key = @{@{$res}[0]}[3];
1925 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1926 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1927 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1928 if ($error == 0 ) {
1929 $send_succeed++ ;
1930 $delivered2host++ ;
1931 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1932 } else {
1933 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1934 }
1936 } else {
1937 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1938 "registrated at server '$registration_server', ".
1939 "but no data available in known_server_db ", 1);
1940 }
1941 }
1943 if (not $delivered2host) {
1944 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1945 }
1946 }
1948 if ($send_succeed) {
1949 # set outgoing msg at db to deliverd
1950 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1951 my $res = $messaging_db->exec_statement($sql);
1952 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1953 } else {
1954 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
1955 }
1956 }
1958 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1959 return;
1960 }
1963 sub watch_for_done_messages {
1964 my ($kernel,$heap) = @_[KERNEL, HEAP];
1966 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1967 #&daemon_log("0 DEBUG: $sql", 7);
1968 my $res = $messaging_db->exec_statement($sql);
1970 foreach my $hit (@{$res}) {
1971 my $msg_id = @{$hit}[0];
1973 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1974 #&daemon_log("0 DEBUG: $sql", 7);
1975 my $res = $messaging_db->exec_statement($sql);
1977 # not all usr msgs have been seen till now
1978 if ( ref(@$res[0]) eq "ARRAY") { next; }
1980 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1981 #&daemon_log("0 DEBUG: $sql", 7);
1982 $res = $messaging_db->exec_statement($sql);
1984 }
1986 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1987 return;
1988 }
1991 sub watch_for_old_known_clients {
1992 my ($kernel,$heap) = @_[KERNEL, HEAP];
1994 my $sql_statement = "SELECT * FROM $known_clients_tn";
1995 my $res = $known_clients_db->select_dbentry( $sql_statement );
1997 my $act_time = int(&get_time());
1999 while ( my ($hit_num, $hit) = each %$res) {
2000 my $expired_timestamp = int($hit->{'timestamp'});
2001 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2002 my $dt = DateTime->new( year => $1,
2003 month => $2,
2004 day => $3,
2005 hour => $4,
2006 minute => $5,
2007 second => $6,
2008 );
2010 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2011 $expired_timestamp = $dt->ymd('').$dt->hms('');
2012 if ($act_time > $expired_timestamp) {
2013 my $hostname = $hit->{'hostname'};
2014 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2015 my $del_res = $known_clients_db->exec_statement($del_sql);
2017 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2018 }
2020 }
2022 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2023 }
2026 sub watch_for_next_tasks {
2027 my ($kernel,$heap) = @_[KERNEL, HEAP];
2029 my $sql = "SELECT * FROM $incoming_tn";
2030 my $res = $incoming_db->select_dbentry($sql);
2032 while ( my ($hit_num, $hit) = each %$res) {
2033 my $headertag = $hit->{'headertag'};
2034 if ($headertag =~ /^answer_(\d+)/) {
2035 # do not start processing, this message is for a still running POE::Wheel
2036 next;
2037 }
2038 my $message_id = $hit->{'id'};
2039 $kernel->yield('next_task', $hit);
2041 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2042 my $res = $incoming_db->exec_statement($sql);
2043 }
2045 $kernel->delay_set('watch_for_next_tasks', 0.1);
2046 }
2049 sub get_ldap_handle {
2050 my ($session_id) = @_;
2051 my $heap;
2052 my $ldap_handle;
2054 if (not defined $session_id ) { $session_id = 0 };
2055 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2057 if ($session_id == 0) {
2058 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2059 $ldap_handle = Net::LDAP->new( $ldap_uri );
2060 $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!");
2062 } else {
2063 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2064 if( defined $session_reference ) {
2065 $heap = $session_reference->get_heap();
2066 }
2068 if (not defined $heap) {
2069 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2070 return;
2071 }
2073 # TODO: This "if" is nonsense, because it doesn't prove that the
2074 # used handle is still valid - or if we've to reconnect...
2075 #if (not exists $heap->{ldap_handle}) {
2076 $ldap_handle = Net::LDAP->new( $ldap_uri );
2077 $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!");
2078 $heap->{ldap_handle} = $ldap_handle;
2079 #}
2080 }
2081 return $ldap_handle;
2082 }
2085 sub change_fai_state {
2086 my ($st, $targets, $session_id) = @_;
2087 $session_id = 0 if not defined $session_id;
2088 # Set FAI state to localboot
2089 my %mapActions= (
2090 reboot => '',
2091 update => 'softupdate',
2092 localboot => 'localboot',
2093 reinstall => 'install',
2094 rescan => '',
2095 wake => '',
2096 memcheck => 'memcheck',
2097 sysinfo => 'sysinfo',
2098 install => 'install',
2099 );
2101 # Return if this is unknown
2102 if (!exists $mapActions{ $st }){
2103 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2104 return;
2105 }
2107 my $state= $mapActions{ $st };
2109 my $ldap_handle = &get_ldap_handle($session_id);
2110 if( defined($ldap_handle) ) {
2112 # Build search filter for hosts
2113 my $search= "(&(objectClass=GOhard)";
2114 foreach (@{$targets}){
2115 $search.= "(macAddress=$_)";
2116 }
2117 $search.= ")";
2119 # If there's any host inside of the search string, procress them
2120 if (!($search =~ /macAddress/)){
2121 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2122 return;
2123 }
2125 # Perform search for Unit Tag
2126 my $mesg = $ldap_handle->search(
2127 base => $ldap_base,
2128 scope => 'sub',
2129 attrs => ['dn', 'FAIstate', 'objectClass'],
2130 filter => "$search"
2131 );
2133 if ($mesg->count) {
2134 my @entries = $mesg->entries;
2135 if (0 == @entries) {
2136 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2137 }
2139 foreach my $entry (@entries) {
2140 # Only modify entry if it is not set to '$state'
2141 if ($entry->get_value("FAIstate") ne "$state"){
2142 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2143 my $result;
2144 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2145 if (exists $tmp{'FAIobject'}){
2146 if ($state eq ''){
2147 $result= $ldap_handle->modify($entry->dn, changes => [
2148 delete => [ FAIstate => [] ] ]);
2149 } else {
2150 $result= $ldap_handle->modify($entry->dn, changes => [
2151 replace => [ FAIstate => $state ] ]);
2152 }
2153 } elsif ($state ne ''){
2154 $result= $ldap_handle->modify($entry->dn, changes => [
2155 add => [ objectClass => 'FAIobject' ],
2156 add => [ FAIstate => $state ] ]);
2157 }
2159 # Errors?
2160 if ($result->code){
2161 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2162 }
2163 } else {
2164 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2165 }
2166 }
2167 } else {
2168 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2169 }
2171 # if no ldap handle defined
2172 } else {
2173 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2174 }
2176 return;
2177 }
2180 sub change_goto_state {
2181 my ($st, $targets, $session_id) = @_;
2182 $session_id = 0 if not defined $session_id;
2184 # Switch on or off?
2185 my $state= $st eq 'active' ? 'active': 'locked';
2187 my $ldap_handle = &get_ldap_handle($session_id);
2188 if( defined($ldap_handle) ) {
2190 # Build search filter for hosts
2191 my $search= "(&(objectClass=GOhard)";
2192 foreach (@{$targets}){
2193 $search.= "(macAddress=$_)";
2194 }
2195 $search.= ")";
2197 # If there's any host inside of the search string, procress them
2198 if (!($search =~ /macAddress/)){
2199 return;
2200 }
2202 # Perform search for Unit Tag
2203 my $mesg = $ldap_handle->search(
2204 base => $ldap_base,
2205 scope => 'sub',
2206 attrs => ['dn', 'gotoMode'],
2207 filter => "$search"
2208 );
2210 if ($mesg->count) {
2211 my @entries = $mesg->entries;
2212 foreach my $entry (@entries) {
2214 # Only modify entry if it is not set to '$state'
2215 if ($entry->get_value("gotoMode") ne $state){
2217 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2218 my $result;
2219 $result= $ldap_handle->modify($entry->dn, changes => [
2220 replace => [ gotoMode => $state ] ]);
2222 # Errors?
2223 if ($result->code){
2224 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2225 }
2227 }
2228 }
2229 } else {
2230 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2231 }
2233 }
2234 }
2237 sub run_recreate_packages_db {
2238 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2239 my $session_id = $session->ID;
2240 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2241 $kernel->yield('create_fai_release_db', $fai_release_tn);
2242 $kernel->yield('create_fai_server_db', $fai_server_tn);
2243 return;
2244 }
2247 sub run_create_fai_server_db {
2248 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2249 my $session_id = $session->ID;
2250 my $task = POE::Wheel::Run->new(
2251 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2252 StdoutEvent => "session_run_result",
2253 StderrEvent => "session_run_debug",
2254 CloseEvent => "session_run_done",
2255 );
2257 $heap->{task}->{ $task->ID } = $task;
2258 return;
2259 }
2262 sub create_fai_server_db {
2263 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2264 my $result;
2266 if (not defined $session_id) { $session_id = 0; }
2267 my $ldap_handle = &get_ldap_handle();
2268 if(defined($ldap_handle)) {
2269 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2270 my $mesg= $ldap_handle->search(
2271 base => $ldap_base,
2272 scope => 'sub',
2273 attrs => ['FAIrepository', 'gosaUnitTag'],
2274 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2275 );
2276 if($mesg->{'resultCode'} == 0 &&
2277 $mesg->count != 0) {
2278 foreach my $entry (@{$mesg->{entries}}) {
2279 if($entry->exists('FAIrepository')) {
2280 # Add an entry for each Repository configured for server
2281 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2282 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2283 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2284 $result= $fai_server_db->add_dbentry( {
2285 table => $table_name,
2286 primkey => ['server', 'release', 'tag'],
2287 server => $tmp_url,
2288 release => $tmp_release,
2289 sections => $tmp_sections,
2290 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2291 } );
2292 }
2293 }
2294 }
2295 }
2296 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2298 # TODO: Find a way to post the 'create_packages_list_db' event
2299 if(not defined($dont_create_packages_list)) {
2300 &create_packages_list_db(undef, undef, $session_id);
2301 }
2302 }
2304 $ldap_handle->disconnect;
2305 return $result;
2306 }
2309 sub run_create_fai_release_db {
2310 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2311 my $session_id = $session->ID;
2312 my $task = POE::Wheel::Run->new(
2313 Program => sub { &create_fai_release_db($table_name, $session_id) },
2314 StdoutEvent => "session_run_result",
2315 StderrEvent => "session_run_debug",
2316 CloseEvent => "session_run_done",
2317 );
2319 $heap->{task}->{ $task->ID } = $task;
2320 return;
2321 }
2324 sub create_fai_release_db {
2325 my ($table_name, $session_id) = @_;
2326 my $result;
2328 # used for logging
2329 if (not defined $session_id) { $session_id = 0; }
2331 my $ldap_handle = &get_ldap_handle();
2332 if(defined($ldap_handle)) {
2333 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2334 my $mesg= $ldap_handle->search(
2335 base => $ldap_base,
2336 scope => 'sub',
2337 attrs => [],
2338 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2339 );
2340 if($mesg->{'resultCode'} == 0 &&
2341 $mesg->count != 0) {
2342 # Walk through all possible FAI container ou's
2343 my @sql_list;
2344 my $timestamp= &get_time();
2345 foreach my $ou (@{$mesg->{entries}}) {
2346 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2347 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2348 my @tmp_array=get_fai_release_entries($tmp_classes);
2349 if(@tmp_array) {
2350 foreach my $entry (@tmp_array) {
2351 if(defined($entry) && ref($entry) eq 'HASH') {
2352 my $sql=
2353 "INSERT INTO $table_name "
2354 ."(timestamp, release, class, type, state) VALUES ("
2355 .$timestamp.","
2356 ."'".$entry->{'release'}."',"
2357 ."'".$entry->{'class'}."',"
2358 ."'".$entry->{'type'}."',"
2359 ."'".$entry->{'state'}."')";
2360 push @sql_list, $sql;
2361 }
2362 }
2363 }
2364 }
2365 }
2367 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2368 if(@sql_list) {
2369 unshift @sql_list, "VACUUM";
2370 unshift @sql_list, "DELETE FROM $table_name";
2371 $fai_release_db->exec_statementlist(\@sql_list);
2372 }
2373 daemon_log("$session_id DEBUG: Done with inserting",7);
2374 }
2375 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2376 }
2377 $ldap_handle->disconnect;
2378 return $result;
2379 }
2381 sub get_fai_types {
2382 my $tmp_classes = shift || return undef;
2383 my @result;
2385 foreach my $type(keys %{$tmp_classes}) {
2386 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2387 my $entry = {
2388 type => $type,
2389 state => $tmp_classes->{$type}[0],
2390 };
2391 push @result, $entry;
2392 }
2393 }
2395 return @result;
2396 }
2398 sub get_fai_state {
2399 my $result = "";
2400 my $tmp_classes = shift || return $result;
2402 foreach my $type(keys %{$tmp_classes}) {
2403 if(defined($tmp_classes->{$type}[0])) {
2404 $result = $tmp_classes->{$type}[0];
2406 # State is equal for all types in class
2407 last;
2408 }
2409 }
2411 return $result;
2412 }
2414 sub resolve_fai_classes {
2415 my ($fai_base, $ldap_handle, $session_id) = @_;
2416 if (not defined $session_id) { $session_id = 0; }
2417 my $result;
2418 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2419 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2420 my $fai_classes;
2422 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2423 my $mesg= $ldap_handle->search(
2424 base => $fai_base,
2425 scope => 'sub',
2426 attrs => ['cn','objectClass','FAIstate'],
2427 filter => $fai_filter,
2428 );
2429 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2431 if($mesg->{'resultCode'} == 0 &&
2432 $mesg->count != 0) {
2433 foreach my $entry (@{$mesg->{entries}}) {
2434 if($entry->exists('cn')) {
2435 my $tmp_dn= $entry->dn();
2437 # Skip classname and ou dn parts for class
2438 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2440 # Skip classes without releases
2441 if((!defined($tmp_release)) || length($tmp_release)==0) {
2442 next;
2443 }
2445 my $tmp_cn= $entry->get_value('cn');
2446 my $tmp_state= $entry->get_value('FAIstate');
2448 my $tmp_type;
2449 # Get FAI type
2450 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2451 if(grep $_ eq $oclass, @possible_fai_classes) {
2452 $tmp_type= $oclass;
2453 last;
2454 }
2455 }
2457 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2458 # A Subrelease
2459 my @sub_releases = split(/,/, $tmp_release);
2461 # Walk through subreleases and build hash tree
2462 my $hash;
2463 while(my $tmp_sub_release = pop @sub_releases) {
2464 $hash .= "\{'$tmp_sub_release'\}->";
2465 }
2466 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2467 } else {
2468 # A branch, no subrelease
2469 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2470 }
2471 } elsif (!$entry->exists('cn')) {
2472 my $tmp_dn= $entry->dn();
2473 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2475 # Skip classes without releases
2476 if((!defined($tmp_release)) || length($tmp_release)==0) {
2477 next;
2478 }
2480 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2481 # A Subrelease
2482 my @sub_releases= split(/,/, $tmp_release);
2484 # Walk through subreleases and build hash tree
2485 my $hash;
2486 while(my $tmp_sub_release = pop @sub_releases) {
2487 $hash .= "\{'$tmp_sub_release'\}->";
2488 }
2489 # Remove the last two characters
2490 chop($hash);
2491 chop($hash);
2493 eval('$fai_classes->'.$hash.'= {}');
2494 } else {
2495 # A branch, no subrelease
2496 if(!exists($fai_classes->{$tmp_release})) {
2497 $fai_classes->{$tmp_release} = {};
2498 }
2499 }
2500 }
2501 }
2503 # The hash is complete, now we can honor the copy-on-write based missing entries
2504 foreach my $release (keys %$fai_classes) {
2505 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2506 }
2507 }
2508 return $result;
2509 }
2511 sub apply_fai_inheritance {
2512 my $fai_classes = shift || return {};
2513 my $tmp_classes;
2515 # Get the classes from the branch
2516 foreach my $class (keys %{$fai_classes}) {
2517 # Skip subreleases
2518 if($class =~ /^ou=.*$/) {
2519 next;
2520 } else {
2521 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2522 }
2523 }
2525 # Apply to each subrelease
2526 foreach my $subrelease (keys %{$fai_classes}) {
2527 if($subrelease =~ /ou=/) {
2528 foreach my $tmp_class (keys %{$tmp_classes}) {
2529 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2530 $fai_classes->{$subrelease}->{$tmp_class} =
2531 deep_copy($tmp_classes->{$tmp_class});
2532 } else {
2533 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2534 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2535 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2536 deep_copy($tmp_classes->{$tmp_class}->{$type});
2537 }
2538 }
2539 }
2540 }
2541 }
2542 }
2544 # Find subreleases in deeper levels
2545 foreach my $subrelease (keys %{$fai_classes}) {
2546 if($subrelease =~ /ou=/) {
2547 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2548 if($subsubrelease =~ /ou=/) {
2549 apply_fai_inheritance($fai_classes->{$subrelease});
2550 }
2551 }
2552 }
2553 }
2555 return $fai_classes;
2556 }
2558 sub get_fai_release_entries {
2559 my $tmp_classes = shift || return;
2560 my $parent = shift || "";
2561 my @result = shift || ();
2563 foreach my $entry (keys %{$tmp_classes}) {
2564 if(defined($entry)) {
2565 if($entry =~ /^ou=.*$/) {
2566 my $release_name = $entry;
2567 $release_name =~ s/ou=//g;
2568 if(length($parent)>0) {
2569 $release_name = $parent."/".$release_name;
2570 }
2571 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2572 foreach my $bufentry(@bufentries) {
2573 push @result, $bufentry;
2574 }
2575 } else {
2576 my @types = get_fai_types($tmp_classes->{$entry});
2577 foreach my $type (@types) {
2578 push @result,
2579 {
2580 'class' => $entry,
2581 'type' => $type->{'type'},
2582 'release' => $parent,
2583 'state' => $type->{'state'},
2584 };
2585 }
2586 }
2587 }
2588 }
2590 return @result;
2591 }
2593 sub deep_copy {
2594 my $this = shift;
2595 if (not ref $this) {
2596 $this;
2597 } elsif (ref $this eq "ARRAY") {
2598 [map deep_copy($_), @$this];
2599 } elsif (ref $this eq "HASH") {
2600 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2601 } else { die "what type is $_?" }
2602 }
2605 sub session_run_result {
2606 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2607 $kernel->sig(CHLD => "child_reap");
2608 }
2610 sub session_run_debug {
2611 my $result = $_[ARG0];
2612 print STDERR "$result\n";
2613 }
2615 sub session_run_done {
2616 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2617 delete $heap->{task}->{$task_id};
2618 }
2621 sub create_sources_list {
2622 my $session_id = shift;
2623 my $ldap_handle = &main::get_ldap_handle;
2624 my $result="/tmp/gosa_si_tmp_sources_list";
2626 # Remove old file
2627 if(stat($result)) {
2628 unlink($result);
2629 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2630 }
2632 my $fh;
2633 open($fh, ">$result");
2634 if (not defined $fh) {
2635 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2636 return undef;
2637 }
2638 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2639 my $mesg=$ldap_handle->search(
2640 base => $main::ldap_server_dn,
2641 scope => 'base',
2642 attrs => 'FAIrepository',
2643 filter => 'objectClass=FAIrepositoryServer'
2644 );
2645 if($mesg->count) {
2646 foreach my $entry(@{$mesg->{'entries'}}) {
2647 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2648 my ($server, $tag, $release, $sections)= split /\|/, $value;
2649 my $line = "deb $server $release";
2650 $sections =~ s/,/ /g;
2651 $line.= " $sections";
2652 print $fh $line."\n";
2653 }
2654 }
2655 }
2656 } else {
2657 if (defined $main::ldap_server_dn){
2658 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2659 } else {
2660 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2661 }
2662 }
2663 close($fh);
2665 return $result;
2666 }
2669 sub run_create_packages_list_db {
2670 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2671 my $session_id = $session->ID;
2673 my $task = POE::Wheel::Run->new(
2674 Priority => +20,
2675 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2676 StdoutEvent => "session_run_result",
2677 StderrEvent => "session_run_debug",
2678 CloseEvent => "session_run_done",
2679 );
2680 $heap->{task}->{ $task->ID } = $task;
2681 }
2684 sub create_packages_list_db {
2685 my ($ldap_handle, $sources_file, $session_id) = @_;
2687 # it should not be possible to trigger a recreation of packages_list_db
2688 # while packages_list_db is under construction, so set flag packages_list_under_construction
2689 # which is tested befor recreation can be started
2690 if (-r $packages_list_under_construction) {
2691 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2692 return;
2693 } else {
2694 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2695 # set packages_list_under_construction to true
2696 system("touch $packages_list_under_construction");
2697 @packages_list_statements=();
2698 }
2700 if (not defined $session_id) { $session_id = 0; }
2701 if (not defined $ldap_handle) {
2702 $ldap_handle= &get_ldap_handle();
2704 if (not defined $ldap_handle) {
2705 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2706 unlink($packages_list_under_construction);
2707 return;
2708 }
2709 }
2710 if (not defined $sources_file) {
2711 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2712 $sources_file = &create_sources_list($session_id);
2713 }
2715 if (not defined $sources_file) {
2716 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2717 unlink($packages_list_under_construction);
2718 return;
2719 }
2721 my $line;
2723 open(CONFIG, "<$sources_file") or do {
2724 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2725 unlink($packages_list_under_construction);
2726 return;
2727 };
2729 # Read lines
2730 while ($line = <CONFIG>){
2731 # Unify
2732 chop($line);
2733 $line =~ s/^\s+//;
2734 $line =~ s/^\s+/ /;
2736 # Strip comments
2737 $line =~ s/#.*$//g;
2739 # Skip empty lines
2740 if ($line =~ /^\s*$/){
2741 next;
2742 }
2744 # Interpret deb line
2745 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2746 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2747 my $section;
2748 foreach $section (split(' ', $sections)){
2749 &parse_package_info( $baseurl, $dist, $section, $session_id );
2750 }
2751 }
2752 }
2754 close (CONFIG);
2757 find(\&cleanup_and_extract, keys( %repo_dirs ));
2758 &main::strip_packages_list_statements();
2759 unshift @packages_list_statements, "VACUUM";
2760 $packages_list_db->exec_statementlist(\@packages_list_statements);
2761 unlink($packages_list_under_construction);
2762 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2763 return;
2764 }
2766 # This function should do some intensive task to minimize the db-traffic
2767 sub strip_packages_list_statements {
2768 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2769 my @new_statement_list=();
2770 my $hash;
2771 my $insert_hash;
2772 my $update_hash;
2773 my $delete_hash;
2774 my $local_timestamp=get_time();
2776 foreach my $existing_entry (@existing_entries) {
2777 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2778 }
2780 foreach my $statement (@packages_list_statements) {
2781 if($statement =~ /^INSERT/i) {
2782 # Assign the values from the insert statement
2783 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2784 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2785 if(exists($hash->{$distribution}->{$package}->{$version})) {
2786 # If section or description has changed, update the DB
2787 if(
2788 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2789 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2790 ) {
2791 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2792 }
2793 } else {
2794 # Insert a non-existing entry to db
2795 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2796 }
2797 } elsif ($statement =~ /^UPDATE/i) {
2798 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2799 /^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;
2800 foreach my $distribution (keys %{$hash}) {
2801 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2802 # update the insertion hash to execute only one query per package (insert instead insert+update)
2803 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2804 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2805 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2806 my $section;
2807 my $description;
2808 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2809 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2810 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2811 }
2812 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2813 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2814 }
2815 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2816 }
2817 }
2818 }
2819 }
2820 }
2822 # TODO: Check for orphaned entries
2824 # unroll the insert_hash
2825 foreach my $distribution (keys %{$insert_hash}) {
2826 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2827 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2828 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2829 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2830 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2831 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2832 ."'$local_timestamp')";
2833 }
2834 }
2835 }
2837 # unroll the update hash
2838 foreach my $distribution (keys %{$update_hash}) {
2839 foreach my $package (keys %{$update_hash->{$distribution}}) {
2840 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2841 my $set = "";
2842 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2843 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2844 }
2845 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2846 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2847 }
2848 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2849 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2850 }
2851 if(defined($set) and length($set) > 0) {
2852 $set .= "timestamp = '$local_timestamp'";
2853 } else {
2854 next;
2855 }
2856 push @new_statement_list,
2857 "UPDATE $main::packages_list_tn SET $set WHERE"
2858 ." distribution = '$distribution'"
2859 ." AND package = '$package'"
2860 ." AND version = '$version'";
2861 }
2862 }
2863 }
2865 @packages_list_statements = @new_statement_list;
2866 }
2869 sub parse_package_info {
2870 my ($baseurl, $dist, $section, $session_id)= @_;
2871 my ($package);
2872 if (not defined $session_id) { $session_id = 0; }
2873 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2874 $repo_dirs{ "${repo_path}/pool" } = 1;
2876 foreach $package ("Packages.gz"){
2877 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2878 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2879 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2880 }
2882 }
2885 sub get_package {
2886 my ($url, $dest, $session_id)= @_;
2887 if (not defined $session_id) { $session_id = 0; }
2889 my $tpath = dirname($dest);
2890 -d "$tpath" || mkpath "$tpath";
2892 # This is ugly, but I've no time to take a look at "how it works in perl"
2893 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2894 system("gunzip -cd '$dest' > '$dest.in'");
2895 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2896 unlink($dest);
2897 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2898 } else {
2899 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2900 }
2901 return 0;
2902 }
2905 sub parse_package {
2906 my ($path, $dist, $srv_path, $session_id)= @_;
2907 if (not defined $session_id) { $session_id = 0;}
2908 my ($package, $version, $section, $description);
2909 my $PACKAGES;
2910 my $timestamp = &get_time();
2912 if(not stat("$path.in")) {
2913 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2914 return;
2915 }
2917 open($PACKAGES, "<$path.in");
2918 if(not defined($PACKAGES)) {
2919 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2920 return;
2921 }
2923 # Read lines
2924 while (<$PACKAGES>){
2925 my $line = $_;
2926 # Unify
2927 chop($line);
2929 # Use empty lines as a trigger
2930 if ($line =~ /^\s*$/){
2931 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2932 push(@packages_list_statements, $sql);
2933 $package = "none";
2934 $version = "none";
2935 $section = "none";
2936 $description = "none";
2937 next;
2938 }
2940 # Trigger for package name
2941 if ($line =~ /^Package:\s/){
2942 ($package)= ($line =~ /^Package: (.*)$/);
2943 next;
2944 }
2946 # Trigger for version
2947 if ($line =~ /^Version:\s/){
2948 ($version)= ($line =~ /^Version: (.*)$/);
2949 next;
2950 }
2952 # Trigger for description
2953 if ($line =~ /^Description:\s/){
2954 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2955 next;
2956 }
2958 # Trigger for section
2959 if ($line =~ /^Section:\s/){
2960 ($section)= ($line =~ /^Section: (.*)$/);
2961 next;
2962 }
2964 # Trigger for filename
2965 if ($line =~ /^Filename:\s/){
2966 my ($filename) = ($line =~ /^Filename: (.*)$/);
2967 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2968 next;
2969 }
2970 }
2972 close( $PACKAGES );
2973 unlink( "$path.in" );
2974 }
2977 sub store_fileinfo {
2978 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2980 my %fileinfo = (
2981 'package' => $package,
2982 'dist' => $dist,
2983 'version' => $vers,
2984 );
2986 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2987 }
2990 sub cleanup_and_extract {
2991 my $fileinfo = $repo_files{ $File::Find::name };
2993 if( defined $fileinfo ) {
2995 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2996 my $sql;
2997 my $package = $fileinfo->{ 'package' };
2998 my $newver = $fileinfo->{ 'version' };
3000 mkpath($dir);
3001 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3003 if( -f "$dir/DEBIAN/templates" ) {
3005 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3007 my $tmpl= "";
3008 {
3009 local $/=undef;
3010 open FILE, "$dir/DEBIAN/templates";
3011 $tmpl = &encode_base64(<FILE>);
3012 close FILE;
3013 }
3014 rmtree("$dir/DEBIAN/templates");
3016 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3017 push @packages_list_statements, $sql;
3018 }
3019 }
3021 return;
3022 }
3025 sub register_at_foreign_servers {
3026 my ($kernel) = $_[KERNEL];
3028 # hole alle bekannten server aus known_server_db
3029 my $server_sql = "SELECT * FROM $known_server_tn";
3030 my $server_res = $known_server_db->exec_statement($server_sql);
3032 # no entries in known_server_db
3033 if (not ref(@$server_res[0]) eq "ARRAY") {
3034 # TODO
3035 }
3037 # detect already connected clients
3038 my $client_sql = "SELECT * FROM $known_clients_tn";
3039 my $client_res = $known_clients_db->exec_statement($client_sql);
3041 # send my server details to all other gosa-si-server within the network
3042 foreach my $hit (@$server_res) {
3043 my $hostname = @$hit[0];
3044 my $hostkey = &create_passwd;
3046 # add already connected clients to registration message
3047 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3048 &add_content2xml_hash($myhash, 'key', $hostkey);
3049 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3051 # add locally loaded gosa-si modules to registration message
3052 my $loaded_modules = {};
3053 while (my ($package, $pck_info) = each %$known_modules) {
3054 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3055 foreach my $act_module (keys(%{@$pck_info[2]})) {
3056 $loaded_modules->{$act_module} = "";
3057 }
3058 }
3060 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3062 # add macaddress to registration message
3063 my ($host_ip, $host_port) = split(/:/, $hostname);
3064 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3065 my $network_interface= &get_interface_for_ip($local_ip);
3066 my $host_mac = &get_mac_for_interface($network_interface);
3067 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3069 # build registration message and send it
3070 my $foreign_server_msg = &create_xml_string($myhash);
3071 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3072 }
3074 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3075 return;
3076 }
3079 #==== MAIN = main ==============================================================
3080 # parse commandline options
3081 Getopt::Long::Configure( "bundling" );
3082 GetOptions("h|help" => \&usage,
3083 "c|config=s" => \$cfg_file,
3084 "f|foreground" => \$foreground,
3085 "v|verbose+" => \$verbose,
3086 "no-arp+" => \$no_arp,
3087 );
3089 # read and set config parameters
3090 &check_cmdline_param ;
3091 &read_configfile($cfg_file, %cfg_defaults);
3092 &check_pid;
3094 $SIG{CHLD} = 'IGNORE';
3096 # forward error messages to logfile
3097 if( ! $foreground ) {
3098 open( STDIN, '+>/dev/null' );
3099 open( STDOUT, '+>&STDIN' );
3100 open( STDERR, '+>&STDIN' );
3101 }
3103 # Just fork, if we are not in foreground mode
3104 if( ! $foreground ) {
3105 chdir '/' or die "Can't chdir to /: $!";
3106 $pid = fork;
3107 setsid or die "Can't start a new session: $!";
3108 umask 0;
3109 } else {
3110 $pid = $$;
3111 }
3113 # Do something useful - put our PID into the pid_file
3114 if( 0 != $pid ) {
3115 open( LOCK_FILE, ">$pid_file" );
3116 print LOCK_FILE "$pid\n";
3117 close( LOCK_FILE );
3118 if( !$foreground ) {
3119 exit( 0 )
3120 };
3121 }
3123 # parse head url and revision from svn
3124 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3125 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3126 $server_headURL = defined $1 ? $1 : 'unknown' ;
3127 $server_revision = defined $2 ? $2 : 'unknown' ;
3128 if ($server_headURL =~ /\/tag\// ||
3129 $server_headURL =~ /\/branches\// ) {
3130 $server_status = "stable";
3131 } else {
3132 $server_status = "developmental" ;
3133 }
3136 daemon_log(" ", 1);
3137 daemon_log("$0 started!", 1);
3138 daemon_log("status: $server_status", 1);
3139 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3141 # connect to incoming_db
3142 unlink($incoming_file_name);
3143 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3144 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3146 # connect to gosa-si job queue
3147 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3148 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3150 # connect to known_clients_db
3151 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3152 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3154 # connect to foreign_clients_db
3155 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3156 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3158 # connect to known_server_db
3159 unlink($known_server_file_name);
3160 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3161 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3163 # connect to login_usr_db
3164 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3165 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3167 # connect to fai_server_db and fai_release_db
3168 unlink($fai_server_file_name);
3169 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3170 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3172 unlink($fai_release_file_name);
3173 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3174 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3176 # connect to packages_list_db
3177 #unlink($packages_list_file_name);
3178 unlink($packages_list_under_construction);
3179 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3180 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3182 # connect to messaging_db
3183 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3184 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3187 # create xml object used for en/decrypting
3188 $xml = new XML::Simple();
3191 # foreign servers
3192 my @foreign_server_list;
3194 # add foreign server from cfg file
3195 if ($foreign_server_string ne "") {
3196 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3197 foreach my $foreign_server (@cfg_foreign_server_list) {
3198 push(@foreign_server_list, $foreign_server);
3199 }
3200 }
3202 # add foreign server from dns
3203 my @tmp_servers;
3204 if ( !$server_domain) {
3205 # Try our DNS Searchlist
3206 for my $domain(get_dns_domains()) {
3207 chomp($domain);
3208 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3209 if(@$tmp_domains) {
3210 for my $tmp_server(@$tmp_domains) {
3211 push @tmp_servers, $tmp_server;
3212 }
3213 }
3214 }
3215 if(@tmp_servers && length(@tmp_servers)==0) {
3216 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3217 }
3218 } else {
3219 @tmp_servers = &get_server_addresses($server_domain);
3220 if( 0 == @tmp_servers ) {
3221 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3222 }
3223 }
3224 foreach my $server (@tmp_servers) {
3225 unshift(@foreign_server_list, $server);
3226 }
3227 # eliminate duplicate entries
3228 @foreign_server_list = &del_doubles(@foreign_server_list);
3229 my $all_foreign_server = join(", ", @foreign_server_list);
3230 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3232 # add all found foreign servers to known_server
3233 my $act_timestamp = &get_time();
3234 foreach my $foreign_server (@foreign_server_list) {
3236 # do not add myself to known_server_db
3237 if (&is_local($foreign_server)) { next; }
3238 ######################################
3240 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3241 primkey=>['hostname'],
3242 hostname=>$foreign_server,
3243 macaddress=>"",
3244 status=>'not_jet_registered',
3245 hostkey=>"none",
3246 loaded_modules => "none",
3247 timestamp=>$act_timestamp,
3248 } );
3249 }
3252 # Import all modules
3253 &import_modules;
3255 # Check wether all modules are gosa-si valid passwd check
3256 &password_check;
3258 # Prepare for using Opsi
3259 if ($opsi_enabled eq "true") {
3260 use JSON::RPC::Client;
3261 use XML::Quote qw(:all);
3262 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3263 $opsi_client = new JSON::RPC::Client;
3264 }
3267 POE::Component::Server::TCP->new(
3268 Alias => "TCP_SERVER",
3269 Port => $server_port,
3270 ClientInput => sub {
3271 my ($kernel, $input) = @_[KERNEL, ARG0];
3272 push(@tasks, $input);
3273 push(@msgs_to_decrypt, $input);
3274 $kernel->yield("msg_to_decrypt");
3275 },
3276 InlineStates => {
3277 msg_to_decrypt => \&msg_to_decrypt,
3278 next_task => \&next_task,
3279 task_result => \&handle_task_result,
3280 task_done => \&handle_task_done,
3281 task_debug => \&handle_task_debug,
3282 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3283 }
3284 );
3286 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3288 # create session for repeatedly checking the job queue for jobs
3289 POE::Session->create(
3290 inline_states => {
3291 _start => \&session_start,
3292 register_at_foreign_servers => \®ister_at_foreign_servers,
3293 sig_handler => \&sig_handler,
3294 next_task => \&next_task,
3295 task_result => \&handle_task_result,
3296 task_done => \&handle_task_done,
3297 task_debug => \&handle_task_debug,
3298 watch_for_next_tasks => \&watch_for_next_tasks,
3299 watch_for_new_messages => \&watch_for_new_messages,
3300 watch_for_delivery_messages => \&watch_for_delivery_messages,
3301 watch_for_done_messages => \&watch_for_done_messages,
3302 watch_for_new_jobs => \&watch_for_new_jobs,
3303 watch_for_modified_jobs => \&watch_for_modified_jobs,
3304 watch_for_done_jobs => \&watch_for_done_jobs,
3305 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3306 watch_for_old_known_clients => \&watch_for_old_known_clients,
3307 create_packages_list_db => \&run_create_packages_list_db,
3308 create_fai_server_db => \&run_create_fai_server_db,
3309 create_fai_release_db => \&run_create_fai_release_db,
3310 recreate_packages_db => \&run_recreate_packages_db,
3311 session_run_result => \&session_run_result,
3312 session_run_debug => \&session_run_debug,
3313 session_run_done => \&session_run_done,
3314 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3315 }
3316 );
3319 POE::Kernel->run();
3320 exit;