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