e274bf58cdc68d4492748e757b9a6900a28df885
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
82 # Variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file,
84 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
85 $arp_activ, $gosa_unit_tag,
86 $GosaPackages_key, $gosa_timeout,
87 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89 $arp_enabled, $arp_interface,
90 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91 $new_systems_ou,
92 );
94 # additional variable which should be globaly accessable
95 our $server_address;
96 our $server_mac_address;
97 our $gosa_address;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 our $known_modules;
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 # holds all incoming decrypted messages
120 our $incoming_db;
121 our $incoming_tn = 'incoming';
122 my $incoming_file_name;
123 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
124 "timestamp DEFAULT 'none'",
125 "headertag DEFAULT 'none'",
126 "targettag DEFAULT 'none'",
127 "xmlmessage DEFAULT 'none'",
128 "module DEFAULT 'none'",
129 "sessionid DEFAULT '0'",
130 );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
137 "timestamp DEFAULT 'none'",
138 "status DEFAULT 'none'",
139 "result DEFAULT 'none'",
140 "progress DEFAULT 'none'",
141 "headertag DEFAULT 'none'",
142 "targettag DEFAULT 'none'",
143 "xmlmessage DEFAULT 'none'",
144 "macaddress DEFAULT 'none'",
145 "plainname DEFAULT 'none'",
146 "siserver DEFAULT 'none'",
147 "modified DEFAULT '0'",
148 );
150 # holds all other gosa-si-server
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients";
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server";
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release";
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386";
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging";
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
197 "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai";
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 # loop delay for job queue to look for opsi jobs
210 my $job_queue_opsi_delay = 10;
211 our $opsi_client;
212 our $opsi_url;
214 # Lifetime of logged in user information. If no update information comes after n seconds,
215 # the user is expeceted to be no longer logged in or the host is no longer running. Because
216 # of this, the user is deleted from login_users_db
217 our $logged_in_user_date_of_expiry = 600;
220 %cfg_defaults = (
221 "general" => {
222 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
223 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
224 },
225 "server" => {
226 "ip" => [\$server_ip, "0.0.0.0"],
227 "port" => [\$server_port, "20081"],
228 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
229 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
230 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
231 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
232 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
233 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
234 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
235 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
236 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
237 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
238 "repo-path" => [\$repo_path, '/srv/www/repository'],
239 "ldap-uri" => [\$ldap_uri, ""],
240 "ldap-base" => [\$ldap_base, ""],
241 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
242 "ldap-admin-password" => [\$ldap_admin_password, ""],
243 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
244 "max-clients" => [\$max_clients, 10],
245 "wol-password" => [\$wake_on_lan_passwd, ""],
246 },
247 "GOsaPackages" => {
248 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
249 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
250 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
251 "key" => [\$GosaPackages_key, "none"],
252 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
253 },
254 "ClientPackages" => {
255 "key" => [\$ClientPackages_key, "none"],
256 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
257 },
258 "ServerPackages"=> {
259 "address" => [\$foreign_server_string, ""],
260 "dns-lookup" => [\$dns_lookup, "true"],
261 "domain" => [\$server_domain, ""],
262 "key" => [\$ServerPackages_key, "none"],
263 "key-lifetime" => [\$foreign_servers_register_delay, 120],
264 "job-synchronization-enabled" => [\$job_synchronization, "true"],
265 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
266 },
267 "ArpHandler" => {
268 "enabled" => [\$arp_enabled, "true"],
269 "interface" => [\$arp_interface, "all"],
270 },
271 "Opsi" => {
272 "enabled" => [\$opsi_enabled, "false"],
273 "server" => [\$opsi_server, "localhost"],
274 "admin" => [\$opsi_admin, "opsi-admin"],
275 "password" => [\$opsi_password, "secret"],
276 },
278 );
281 #=== FUNCTION ================================================================
282 # NAME: usage
283 # PARAMETERS: nothing
284 # RETURNS: nothing
285 # DESCRIPTION: print out usage text to STDERR
286 #===============================================================================
287 sub usage {
288 print STDERR << "EOF" ;
289 usage: $prg [-hvf] [-c config]
291 -h : this (help) message
292 -c <file> : config file
293 -f : foreground, process will not be forked to background
294 -v : be verbose (multiple to increase verbosity)
295 -no-arp : starts $prg without connection to arp module
297 EOF
298 print "\n" ;
299 }
302 #=== FUNCTION ================================================================
303 # NAME: logging
304 # PARAMETERS: level - string - default 'info'
305 # msg - string -
306 # facility - string - default 'LOG_DAEMON'
307 # RETURNS: nothing
308 # DESCRIPTION: function for logging
309 #===============================================================================
310 sub daemon_log {
311 # log into log_file
312 my( $msg, $level ) = @_;
313 if(not defined $msg) { return }
314 if(not defined $level) { $level = 1 }
315 if(defined $log_file){
316 open(LOG_HANDLE, ">>$log_file");
317 chmod 0600, $log_file;
318 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
319 print STDERR "cannot open $log_file: $!";
320 return
321 }
322 chomp($msg);
323 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
324 if($level <= $verbose){
325 my ($seconds, $minutes, $hours, $monthday, $month,
326 $year, $weekday, $yearday, $sommertime) = localtime(time);
327 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
328 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
329 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
330 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
331 $month = $monthnames[$month];
332 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
333 $year+=1900;
334 my $name = $prg;
336 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
337 print LOG_HANDLE $log_msg;
338 if( $foreground ) {
339 print STDERR $log_msg;
340 }
341 }
342 close( LOG_HANDLE );
343 }
344 }
347 #=== FUNCTION ================================================================
348 # NAME: check_cmdline_param
349 # PARAMETERS: nothing
350 # RETURNS: nothing
351 # DESCRIPTION: validates commandline parameter
352 #===============================================================================
353 sub check_cmdline_param () {
354 my $err_config;
355 my $err_counter = 0;
356 if(not defined($cfg_file)) {
357 $cfg_file = "/etc/gosa-si/server.conf";
358 if(! -r $cfg_file) {
359 $err_config = "please specify a config file";
360 $err_counter += 1;
361 }
362 }
363 if( $err_counter > 0 ) {
364 &usage( "", 1 );
365 if( defined( $err_config)) { print STDERR "$err_config\n"}
366 print STDERR "\n";
367 exit( -1 );
368 }
369 }
372 #=== FUNCTION ================================================================
373 # NAME: check_pid
374 # PARAMETERS: nothing
375 # RETURNS: nothing
376 # DESCRIPTION: handels pid processing
377 #===============================================================================
378 sub check_pid {
379 $pid = -1;
380 # Check, if we are already running
381 if( open(LOCK_FILE, "<$pid_file") ) {
382 $pid = <LOCK_FILE>;
383 if( defined $pid ) {
384 chomp( $pid );
385 if( -f "/proc/$pid/stat" ) {
386 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
387 if( $stat ) {
388 daemon_log("ERROR: Already running",1);
389 close( LOCK_FILE );
390 exit -1;
391 }
392 }
393 }
394 close( LOCK_FILE );
395 unlink( $pid_file );
396 }
398 # create a syslog msg if it is not to possible to open PID file
399 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
400 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
401 if (open(LOCK_FILE, '<', $pid_file)
402 && ($pid = <LOCK_FILE>))
403 {
404 chomp($pid);
405 $msg .= "(PID $pid)\n";
406 } else {
407 $msg .= "(unable to read PID)\n";
408 }
409 if( ! ($foreground) ) {
410 openlog( $0, "cons,pid", "daemon" );
411 syslog( "warning", $msg );
412 closelog();
413 }
414 else {
415 print( STDERR " $msg " );
416 }
417 exit( -1 );
418 }
419 }
421 #=== FUNCTION ================================================================
422 # NAME: import_modules
423 # PARAMETERS: module_path - string - abs. path to the directory the modules
424 # are stored
425 # RETURNS: nothing
426 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
427 # state is on is imported by "require 'file';"
428 #===============================================================================
429 sub import_modules {
430 daemon_log(" ", 1);
432 if (not -e $modules_path) {
433 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
434 }
436 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
437 while (defined (my $file = readdir (DIR))) {
438 if (not $file =~ /(\S*?).pm$/) {
439 next;
440 }
441 my $mod_name = $1;
443 # ArpHandler switch
444 if( $file =~ /ArpHandler.pm/ ) {
445 if( $arp_enabled eq "false" ) { next; }
446 }
448 eval { require $file; };
449 if ($@) {
450 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
451 daemon_log("$@", 5);
452 } else {
453 my $info = eval($mod_name.'::get_module_info()');
454 # Only load module if get_module_info() returns a non-null object
455 if( $info ) {
456 my ($input_address, $input_key, $event_hash) = @{$info};
457 $known_modules->{$mod_name} = $info;
458 daemon_log("0 INFO: module $mod_name loaded", 5);
459 }
460 }
461 }
463 close (DIR);
464 }
466 #=== FUNCTION ================================================================
467 # NAME: password_check
468 # PARAMETERS: nothing
469 # RETURNS: nothing
470 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
471 # the same password
472 #===============================================================================
473 sub password_check {
474 my $passwd_hash = {};
475 while (my ($mod_name, $mod_info) = each %$known_modules) {
476 my $mod_passwd = @$mod_info[1];
477 if (not defined $mod_passwd) { next; }
478 if (not exists $passwd_hash->{$mod_passwd}) {
479 $passwd_hash->{$mod_passwd} = $mod_name;
481 # escalates critical error
482 } else {
483 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
484 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
485 exit( -1 );
486 }
487 }
489 }
492 #=== FUNCTION ================================================================
493 # NAME: sig_int_handler
494 # PARAMETERS: signal - string - signal arose from system
495 # RETURNS: nothing
496 # DESCRIPTION: handels tasks to be done befor signal becomes active
497 #===============================================================================
498 sub sig_int_handler {
499 my ($signal) = @_;
501 # if (defined($ldap_handle)) {
502 # $ldap_handle->disconnect;
503 # }
504 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
507 daemon_log("shutting down gosa-si-server", 1);
508 system("kill `ps -C gosa-si-server -o pid=`");
509 }
510 $SIG{INT} = \&sig_int_handler;
513 sub check_key_and_xml_validity {
514 my ($crypted_msg, $module_key, $session_id) = @_;
515 my $msg;
516 my $msg_hash;
517 my $error_string;
518 eval{
519 $msg = &decrypt_msg($crypted_msg, $module_key);
521 if ($msg =~ /<xml>/i){
522 $msg =~ s/\s+/ /g; # just for better daemon_log
523 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
524 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
526 ##############
527 # check header
528 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
529 my $header_l = $msg_hash->{'header'};
530 if( 1 > @{$header_l} ) { die 'empty header tag'; }
531 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
532 my $header = @{$header_l}[0];
533 if( 0 == length $header) { die 'empty string in header tag'; }
535 ##############
536 # check source
537 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
538 my $source_l = $msg_hash->{'source'};
539 if( 1 > @{$source_l} ) { die 'empty source tag'; }
540 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
541 my $source = @{$source_l}[0];
542 if( 0 == length $source) { die 'source error'; }
544 ##############
545 # check target
546 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
547 my $target_l = $msg_hash->{'target'};
548 if( 1 > @{$target_l} ) { die 'empty target tag'; }
549 }
550 };
551 if($@) {
552 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
553 $msg = undef;
554 $msg_hash = undef;
555 }
557 return ($msg, $msg_hash);
558 }
561 sub check_outgoing_xml_validity {
562 my ($msg, $session_id) = @_;
564 my $msg_hash;
565 eval{
566 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
568 ##############
569 # check header
570 my $header_l = $msg_hash->{'header'};
571 if( 1 != @{$header_l} ) {
572 die 'no or more than one headers specified';
573 }
574 my $header = @{$header_l}[0];
575 if( 0 == length $header) {
576 die 'header has length 0';
577 }
579 ##############
580 # check source
581 my $source_l = $msg_hash->{'source'};
582 if( 1 != @{$source_l} ) {
583 die 'no or more than 1 sources specified';
584 }
585 my $source = @{$source_l}[0];
586 if( 0 == length $source) {
587 die 'source has length 0';
588 }
589 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
590 $source =~ /^GOSA$/i ) {
591 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
592 }
594 ##############
595 # check target
596 my $target_l = $msg_hash->{'target'};
597 if( 0 == @{$target_l} ) {
598 die "no targets specified";
599 }
600 foreach my $target (@$target_l) {
601 if( 0 == length $target) {
602 die "target has length 0";
603 }
604 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
605 $target =~ /^GOSA$/i ||
606 $target =~ /^\*$/ ||
607 $target =~ /KNOWN_SERVER/i ||
608 $target =~ /JOBDB/i ||
609 $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 ){
610 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
611 }
612 }
613 };
614 if($@) {
615 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
616 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
617 $msg_hash = undef;
618 }
620 return ($msg_hash);
621 }
624 sub input_from_known_server {
625 my ($input, $remote_ip, $session_id) = @_ ;
626 my ($msg, $msg_hash, $module);
628 my $sql_statement= "SELECT * FROM known_server";
629 my $query_res = $known_server_db->select_dbentry( $sql_statement );
631 while( my ($hit_num, $hit) = each %{ $query_res } ) {
632 my $host_name = $hit->{hostname};
633 if( not $host_name =~ "^$remote_ip") {
634 next;
635 }
636 my $host_key = $hit->{hostkey};
637 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
638 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
640 # check if module can open msg envelope with module key
641 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
642 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
643 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
644 daemon_log("$@", 8);
645 next;
646 }
647 else {
648 $msg = $tmp_msg;
649 $msg_hash = $tmp_msg_hash;
650 $module = "ServerPackages";
651 last;
652 }
653 }
655 if( (!$msg) || (!$msg_hash) || (!$module) ) {
656 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
657 }
659 return ($msg, $msg_hash, $module);
660 }
663 sub input_from_known_client {
664 my ($input, $remote_ip, $session_id) = @_ ;
665 my ($msg, $msg_hash, $module);
667 my $sql_statement= "SELECT * FROM known_clients";
668 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
669 while( my ($hit_num, $hit) = each %{ $query_res } ) {
670 my $host_name = $hit->{hostname};
671 if( not $host_name =~ /^$remote_ip:\d*$/) {
672 next;
673 }
674 my $host_key = $hit->{hostkey};
675 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
676 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
678 # check if module can open msg envelope with module key
679 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
681 if( (!$msg) || (!$msg_hash) ) {
682 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
683 &daemon_log("$@", 8);
684 next;
685 }
686 else {
687 $module = "ClientPackages";
688 last;
689 }
690 }
692 if( (!$msg) || (!$msg_hash) || (!$module) ) {
693 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
694 }
696 return ($msg, $msg_hash, $module);
697 }
700 sub input_from_unknown_host {
701 no strict "refs";
702 my ($input, $session_id) = @_ ;
703 my ($msg, $msg_hash, $module);
704 my $error_string;
706 my %act_modules = %$known_modules;
708 while( my ($mod, $info) = each(%act_modules)) {
710 # check a key exists for this module
711 my $module_key = ${$mod."_key"};
712 if( not defined $module_key ) {
713 if( $mod eq 'ArpHandler' ) {
714 next;
715 }
716 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
717 next;
718 }
719 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
721 # check if module can open msg envelope with module key
722 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
723 if( (not defined $msg) || (not defined $msg_hash) ) {
724 daemon_log("$session_id ERROR: no msg returned!", 2) if ((not defined $msg) || "" eq $msg);
725 daemon_log("$session_id ERROR: no msg_hash returned!", 2) if ((not defined $msg_hash) || "" eq $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 }
3141 daemon_log(" ", 1);
3142 daemon_log("$0 started!", 1);
3143 daemon_log("status: $server_status", 1);
3144 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3146 # connect to incoming_db
3147 unlink($incoming_file_name);
3148 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3149 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3151 # connect to gosa-si job queue
3152 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3153 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3155 # connect to known_clients_db
3156 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3157 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3159 # connect to foreign_clients_db
3160 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3161 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3163 # connect to known_server_db
3164 unlink($known_server_file_name);
3165 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3166 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3168 # connect to login_usr_db
3169 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3170 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3172 # connect to fai_server_db and fai_release_db
3173 unlink($fai_server_file_name);
3174 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3175 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3177 unlink($fai_release_file_name);
3178 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3179 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3181 # connect to packages_list_db
3182 #unlink($packages_list_file_name);
3183 unlink($packages_list_under_construction);
3184 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3185 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3187 # connect to messaging_db
3188 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3189 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3192 # create xml object used for en/decrypting
3193 $xml = new XML::Simple();
3196 # foreign servers
3197 my @foreign_server_list;
3199 # add foreign server from cfg file
3200 if ($foreign_server_string ne "") {
3201 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3202 foreach my $foreign_server (@cfg_foreign_server_list) {
3203 push(@foreign_server_list, $foreign_server);
3204 }
3206 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3207 }
3209 # Perform a DNS lookup for server registration if flag is true
3210 if ($dns_lookup eq "true") {
3211 # Add foreign server from dns
3212 my @tmp_servers;
3213 if (not $server_domain) {
3214 # Try our DNS Searchlist
3215 for my $domain(get_dns_domains()) {
3216 chomp($domain);
3217 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3218 if(@$tmp_domains) {
3219 for my $tmp_server(@$tmp_domains) {
3220 push @tmp_servers, $tmp_server;
3221 }
3222 }
3223 }
3224 if(@tmp_servers && length(@tmp_servers)==0) {
3225 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3226 }
3227 } else {
3228 @tmp_servers = &get_server_addresses($server_domain);
3229 if( 0 == @tmp_servers ) {
3230 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3231 }
3232 }
3234 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3236 foreach my $server (@tmp_servers) {
3237 unshift(@foreign_server_list, $server);
3238 }
3239 } else {
3240 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3241 }
3244 # eliminate duplicate entries
3245 @foreign_server_list = &del_doubles(@foreign_server_list);
3246 my $all_foreign_server = join(", ", @foreign_server_list);
3247 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3249 # add all found foreign servers to known_server
3250 my $act_timestamp = &get_time();
3251 foreach my $foreign_server (@foreign_server_list) {
3253 # do not add myself to known_server_db
3254 if (&is_local($foreign_server)) { next; }
3255 ######################################
3257 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3258 primkey=>['hostname'],
3259 hostname=>$foreign_server,
3260 macaddress=>"",
3261 status=>'not_jet_registered',
3262 hostkey=>"none",
3263 loaded_modules => "none",
3264 timestamp=>$act_timestamp,
3265 } );
3266 }
3269 # Import all modules
3270 &import_modules;
3272 # Check wether all modules are gosa-si valid passwd check
3273 &password_check;
3275 # Prepare for using Opsi
3276 if ($opsi_enabled eq "true") {
3277 use JSON::RPC::Client;
3278 use XML::Quote qw(:all);
3279 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3280 $opsi_client = new JSON::RPC::Client;
3281 }
3284 POE::Component::Server::TCP->new(
3285 Alias => "TCP_SERVER",
3286 Port => $server_port,
3287 ClientInput => sub {
3288 my ($kernel, $input) = @_[KERNEL, ARG0];
3289 push(@tasks, $input);
3290 push(@msgs_to_decrypt, $input);
3291 $kernel->yield("msg_to_decrypt");
3292 },
3293 InlineStates => {
3294 msg_to_decrypt => \&msg_to_decrypt,
3295 next_task => \&next_task,
3296 task_result => \&handle_task_result,
3297 task_done => \&handle_task_done,
3298 task_debug => \&handle_task_debug,
3299 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3300 }
3301 );
3303 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3305 # create session for repeatedly checking the job queue for jobs
3306 POE::Session->create(
3307 inline_states => {
3308 _start => \&session_start,
3309 register_at_foreign_servers => \®ister_at_foreign_servers,
3310 sig_handler => \&sig_handler,
3311 next_task => \&next_task,
3312 task_result => \&handle_task_result,
3313 task_done => \&handle_task_done,
3314 task_debug => \&handle_task_debug,
3315 watch_for_next_tasks => \&watch_for_next_tasks,
3316 watch_for_new_messages => \&watch_for_new_messages,
3317 watch_for_delivery_messages => \&watch_for_delivery_messages,
3318 watch_for_done_messages => \&watch_for_done_messages,
3319 watch_for_new_jobs => \&watch_for_new_jobs,
3320 watch_for_modified_jobs => \&watch_for_modified_jobs,
3321 watch_for_done_jobs => \&watch_for_done_jobs,
3322 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3323 watch_for_old_known_clients => \&watch_for_old_known_clients,
3324 create_packages_list_db => \&run_create_packages_list_db,
3325 create_fai_server_db => \&run_create_fai_server_db,
3326 create_fai_release_db => \&run_create_fai_release_db,
3327 recreate_packages_db => \&run_recreate_packages_db,
3328 session_run_result => \&session_run_result,
3329 session_run_debug => \&session_run_debug,
3330 session_run_done => \&session_run_done,
3331 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3332 }
3333 );
3336 POE::Kernel->run();
3337 exit;