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