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